Kolejny o certyfikatach. Powiedzmy, że piszesz aplikację na iOS, która ma uwierzytelniać się za pomocą certyfikatu. Implementujesz coś takiego:
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { if ([challenge previousFailureCount] > 0) { NSLog(@"Incorrect auth challenge %@", challenge); [[challenge sender] cancelAuthenticationChallenge:challenge]; return; } // Checking the server certificate if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodClientCertificate && self.account.clientCertificate.privateRepresentation.length > 0) { /* Reading the certificate and creating the identity */ NSData *p12Data = self.account.clientCertificate.privateRepresentation; CFStringRef password = (__bridge CFStringRef)(self.account.clientCertificate.privatePassword); const void *keys[] = { kSecImportExportPassphrase }; const void *values[] = { password }; CFDictionaryRef optionsDictionary = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL); CFArrayRef p12Items; OSStatus result = SecPKCS12Import((__bridge 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:(__bridge NSArray *)myCerts persistence:NSURLCredentialPersistencePermanent]; CFRelease(myCerts); [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge]; } else { // Certificate is invalid or password is invalid given the certificate NSLog(@"Invalid certificate or password"); return; } } else { // For normal authentication based on username and password. This could be NTLM or Default. if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodClientCertificate) { NSLog(@"Certificate might be required"); } NSURLCredential *credential = [NSURLCredential credentialWithUser:self.account.username password:self.account.password persistence:NSURLCredentialPersistenceNone]; [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge]; } }
Wszystko wygląda dobrze na pierwszy rzut oka. W większości przypadków nawet będzie działało. Jeżeli jednak trafisz na problem ze swoim serwerem i sprawdzisz jak to wygląda w Wiresharku:
Widzimy, że coś nie jest do końca w porządku. Klient wysłał ten sam certyfikat dwa razy. Ogólnie nie powinno być to problemem, ale jeżeli Twój serwer przetwarza certyfikaty i spodziewa się, że będą uporządkowane w łańcuch: certyfikat klienta, wydawca certyfikatu klienta itd. – wtedy mogą być problemy.
Problem tego typu można rozwiązać po stronie klienta lub po stronie serwera. Jednak najpoprawniejsze wydaje mi się naprawienie go po stronie klienta. Zgodnie z RFC2246 i RFC5246 certyfikat klienta powinien wystąpić tylko raz:
certificate_list
This is a sequence (chain) of X.509v3 certificates. The sender’s
certificate must come first in the list. Each following
certificate must directly certify the one preceding it. Because
certificate validation requires that root keys be distributed
independently, the self-signed certificate which specifies the
root certificate authority may optionally be omitted from the
chain, under the assumption that the remote end must already
possess it in order to validate it in any case.
The same message type and structure will be used for the client’s
response to a certificate request message. Note that a client may
send no certificates if it does not have an appropriate certificate
to send in response to the server’s authentication request.
Na szczÄĹcie poprawienie tego problemu jest trywialne. Zamieniamy tÄ linijkÄ:
NSURLCredential *credential = [NSURLCredential credentialWithIdentity:identityApp certificates:(__bridge NSArray *)myCerts persistence:NSURLCredentialPersistencePermanent];
na:
NSURLCredential *credential = [NSURLCredential credentialWithIdentity:identityApp certificates:nil persistence:NSURLCredentialPersistencePermanent];
Testujemy, wszystko wygląda poprawnie. Problem rozwią zany bez kosztownych zmian w kodzie serwera.