Bliźniacze certyfikaty z iOS

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:

iOS certs

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.

This post is also available in: English polski

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *