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.
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 ... –
Non ancora, devo lavorare su altre attività ad alta priorità a causa di diversi motivi. Ti contatterò se avrò una soluzione. – alfared
Si tratta di un errore di protocollo PITA iOS. (PITA sta per dolore nel ....) – Josh