2015-09-15 19 views
9

Desidero effettuare una richiesta HTTPS al server personalizzato con certificato autofirmato. Sto usando le sfide di classe e di autenticazione di trasformazione NSURLConnection, ma sempre ricevere il messaggio di errore in una console:Richiesta HTTPS in iOS 9 con certificato autofirmato

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

allora il metodo "di collegamento: didFailWithError:" viene chiamato con il seguente errore:

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} 

App riceve due sfide autenticazione (NSURLAuthenticationMethodClientCertificate e NSURLAuthenticationMethodServerTrust) e li elabora in modo seguente:

- (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]; 
} 

Nel log diagnostico di CFNetwork posso vedere che la procedura di Handshake sta per essere avviata. Almeno l'app invia il messaggio "ClientHello", quindi il server invia il messaggio "ServerHello" e richiede l'autenticazione. E qui l'app tenta di inviare una risposta di autenticazione, ma riceve immediatamente un errore. (Allo stesso tempo, nei log del server non vedo affatto messaggi sull'handshake). Ecco parte del registro di diagnostica:

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] 

Il nostro esempio di back-end può essere installato su lato cliente, quindi non posso impostare alcuna eccezione dominio nel file Info.plist. Anche l'app può richiedere il server per indirizzo IP in formato IPv4, ma non per nome di dominio (come nel mio esempio).

Che cosa ho provato:

  • utilizzato NSURLSession invece di NSURLConnection, ma senza alcun successo;
  • controllato i requisiti ATS di Apple per l'implementazione del server here (lo sviluppatore back-end è sicuro che la sua implementazione soddisfa tutti);
  • giocato con i certificati di ancoraggio delle impostazioni per la convalida della fiducia in conformità con i vari problemi risolti da StackOverflow e dai forum degli sviluppatori Apple;
  • ha prestato particolare attenzione al post similar e al relativo solution nei forum degli sviluppatori;

Sto testando la richiesta https su iPad Air 2 con iOS 9 GM Seed (Build 13A340) e xCode 7 GM Seed (Build 7A218). Nota importante: questa funzionalità funziona bene con iOS 8. Tenendo conto di ciò, posso supporre che il problema sia nel nostro server, ma il nostro sviluppatore di back-end mi ha assicurato che tutto va bene.

Ora sono fuori dalle idee. Apprezzerei se qualcuno potesse darmi un suggerimento, o almeno suggerire qualche altra diagnosi, che rivelerebbe un errore particolare, più specifico di "avviso fatale".

Grazie.

MODIFICA 1: SecTrustEvaluate restituisce sempre kSecTrustResultRecoverableTrustFailure, è per questo motivo che ho dovuto trovare qualche tipo di soluzione alternativa.

+0

Hai trovato una soluzione? Sto avendo questo problema anche io e vorrei usare il mio server locale per i test ma con un certificato autofirmato è impossibile lavorare e ottengo i tuoi stessi errori ... –

+0

Non ancora, devo lavorare su altre attività ad alta priorità a causa di diversi motivi. Ti contatterò se avrò una soluzione. – alfared

+0

Si tratta di un errore di protocollo PITA iOS. (PITA sta per dolore nel ....) – Josh

risposta

0

Questo problema è stato risolto qualche tempo fa. Si è rivelato certificato autofirmato non valido. Non ha soddisfatto tutti i requisiti di Apple. Sfortunatamente non lo so, che cosa era esattamente.

3

Hai usato nscurl per diagnosticare il problema di connessione? Se avete un Mac OS X in esecuzione V10.11 è possibile eseguire qualcosa di simile:

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

In alternativa, se non si dispone di 10.11, è possibile scaricare il codice di esempio qui: https://developer.apple.com/library/mac/samplecode/SC1236/ e costruire con XCode e correre in questo modo (cambiare il percorso a seconda della macchina):

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

(Per trovare il percorso completo per quanto sopra, dopo hai creato, apri il gruppo Prodotti nel tuo Navigatore progetto, fai clic con il tasto destro su TLSTool e "Mostra nel Finder".)

Hai già collegato la tecnologia Apple su questo argomento, https://developer.apple.com/library/prerelease/ios/technotes/App-Transport-Security-Technote/ ma non hai detto se corse nscurl o no.

+0

L'endpoint della mia VM passa tutti i test in/usr/bin/nscurl --ats-diagnostics [https: //mydomain.local: 8888] (https: // mydomain. locale: 8888) ma continuo a ricevere l'errore nel simulatore. –

3

Secondo questo: https://forums.developer.apple.com/message/36842#36842

L'approccio migliore per risolvere carico HTTP fallito (kCFStreamErrorDomainSSL, -9802) è quello di impostare un'eccezione nel file Info.plist come segue:

<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> 

L'importante Il punto è che questo non è meno sicuro di iOS8, ma non altrettanto sicuro di ATS completo supportato da iOS9.

+1

Grazie - ha funzionato alla grande dopo aver chiuso i tag dict. – dlw

+1

Grazie mille, ma: 1. Ciò consente il normale download di HTTP e ho bisogno di HTTPS (per ragioni di sicurezza). 2. Come ho scritto, non posso impostare nessun dominio nell'elenco delle eccezioni, perché i nostri clienti installano spesso il back-end sui propri server. Ad esempio, può essere "server.company1.com" e "server.company2.com". La tua risposta presuppone che io debba ricostruire l'app e aggiungere un nuovo dominio di eccezione "server.company3.com" dopo che vendiamo l'app a una terza società. – alfared

+0

Potrebbe essere possibile utilizzare una combinazione di informazioni dalla documentazione Apple su ATS così come questo post sul blog sui certificati autenticati # 5 in particolare per risolvere il tuo problema (probabilmente dovrai ripetere tutte le volte fino a quando non lo farai bene). – spirographer

0

Ho appena incontrato lo stesso problema con i ur's.Now ripara it.It è perché la versione TLS e il certificato sign.As documento della mela dire sotto apple's document

così faccio questa cosa info.plist setting

e funziona

Problemi correlati