SecCertificateRef: ¿Cómo get la información del certificate?

Tengo un certificate (SecCertificateRef), puedo verificar si es válido y puedo extraer un "resumen" usando SecCertificateCopySubjectSummary.

¿Qué es exactamente el "resumen"? No entiendo el término "Una cadena que contiene un resumen legible por humanos del contenido del certificate". en la documentation de Apple. Creo que se refieren al "CN" en el certificate, ¿correcto?

¿Hay algún método para get la clara información X509 de SecCertificateRef? ¿Ayuda un elenco a un object de llavero?

Quiero tener algo como esto y estoy especialmente centrado en el "CN" para compararlo con la URL que presenté para evitar los ataques de hombre en el medio. (¿O alguna mejor idea?)

Eso es lo que quiero tener:

Version: 3 (0x2) Serial Number: 1 (0x1) Signature Algorithm: md5WithRSAEncryption Issuer: C=XY, ST=Austria, L=Graz, O=TrustMe Ltd, OU=Certificate Authority, CN=CA/Email=ca@trustme.dom Validity Not Before: Oct 29 17:39:10 2000 GMT Not After : Oct 29 17:39:10 2001 GMT Subject: C=DE, ST=Austria, L=Vienna, O=Home, OU=Web Lab, CN=anywhere.com/Email=xyz@anywhere.com Subject Public Key Info: Public Key Algorithm: rsaEncryption RSA Public Key: (1024 bit) Modulus (1024 bit): 00:c4:40:4c:6e:14:1b:61:36:84:24:b2:61:c0:b5: d7:e4:7a:a5:4b:94:ef:d9:5e:43:7f:c1:64:80:fd: 9f:50:41:6b:70:73:80:48:90:f3:58:bf:f0:4c:b9: 90:32:81:59:18:16:3f:19:f4:5f:11:68:36:85:f6: 1c:a9:af:fa:a9:a8:7b:44:85:79:b5:f1:20:d3:25: 7d:1c:de:68:15:0c:b6:bc:59:46:0a:d8:99:4e:07: 50:0a:5d:83:61:d4:db:c9:7d:c3:2e:eb:0a:8f:62: 8f:7e:00:e1:37:67:3f:36:d5:04:38:44:44:77:e9: f0:b4:95:f5:f9:34:9f:f8:43 Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Subject Alternative Name: email:xyz@anywhere.com Netscape Comment: mod_ssl generated test server certificate Netscape Cert Type: SSL Server Signature Algorithm: md5WithRSAEncryption 12:ed:f7:b3:5e:a0:93:3f:a0:1d:60:cb:47:19:7d:15:59:9b: 3b:2c:a8:a3:6a:03:43:d0:85:d3:86:86:2f:e3:aa:79:39:e7: 82:20:ed:f4:11:85:a3:41:5e:5c:8d:36:a2:71:b6:6a:08:f9: cc:1e:da:c4:78:05:75:8f:9b:10:f0:15:f0:9e:67:a0:4e:a1: 4d:3f:16:4c:9b:19:56:6a:f2:af:89:54:52:4a:06:34:42:0d: d5:40:25:6b:b0:c0:a2:03:18:cd:d1:07:20:b6:e5:c5:1e:21: 44:e7:c5:09:d2:d5:94:9d:6c:13:07:2f:3b:7c:4c:64:90:bf: ff:8e 

No pude esperar una respuesta a la recompensa, así que encontré una solución. Como otros han dicho, Security.framework no le proporciona una forma de get esta información, por lo que debe pedirle a OpenSSL que analice los datos de los certificates por usted:

 #import <openssl/x509.h> // ... NSData *certificateData = (NSData *) SecCertificateCopyData(certificate); const unsigned char *certificateDataBytes = (const unsigned char *)[certificateData bytes]; X509 *certificateX509 = d2i_X509(NULL, &certificateDataBytes, [certificateData length]); NSString *issuer = CertificateGetIssuerName(certificateX509); NSDate *expiryDate = CertificateGetExpiryDate(certificateX509); 

Donde CertificateGetIssuerName y CertificateGetExpiryDate son los siguientes:

 static NSString * CertificateGetIssuerName(X509 *certificateX509) { NSString *issuer = nil; if (certificateX509 != NULL) { X509_NAME *issuerX509Name = X509_get_issuer_name(certificateX509); if (issuerX509Name != NULL) { int nid = OBJ_txt2nid("O"); // organization int index = X509_NAME_get_index_by_NID(issuerX509Name, nid, -1); X509_NAME_ENTRY *issuerNameEntry = X509_NAME_get_entry(issuerX509Name, index); if (issuerNameEntry) { ASN1_STRING *issuerNameASN1 = X509_NAME_ENTRY_get_data(issuerNameEntry); if (issuerNameASN1 != NULL) { unsigned char *issuerName = ASN1_STRING_data(issuerNameASN1); issuer = [NSString stringWithUTF8String:(char *)issuerName]; } } } } return issuer; } static NSDate *CertificateGetExpiryDate(X509 *certificateX509) { NSDate *expiryDate = nil; if (certificateX509 != NULL) { ASN1_TIME *certificateExpiryASN1 = X509_get_notAfter(certificateX509); if (certificateExpiryASN1 != NULL) { ASN1_GENERALIZEDTIME *certificateExpiryASN1Generalized = ASN1_TIME_to_generalizedtime(certificateExpiryASN1, NULL); if (certificateExpiryASN1Generalized != NULL) { unsigned char *certificateExpiryData = ASN1_STRING_data(certificateExpiryASN1Generalized); // ASN1 generalized times look like this: "20131114230046Z" // format: YYYYMMDDHHMMSS // indices: 01234567890123 // 1111 // There are other formats (eg specifying partial seconds or // time zones) but this is good enough for our purposes since // we only use the date and not the time. // // (Source: http://www.obj-sys.com/asn1tutorial/node14.html) NSString *expiryTimeStr = [NSString stringWithUTF8String:(char *)certificateExpiryData]; NSDateComponents *expiryDateComponents = [[NSDateComponents alloc] init]; expiryDateComponents.year = [[expiryTimeStr substringWithRange:NSMakeRange(0, 4)] intValue]; expiryDateComponents.month = [[expiryTimeStr substringWithRange:NSMakeRange(4, 2)] intValue]; expiryDateComponents.day = [[expiryTimeStr substringWithRange:NSMakeRange(6, 2)] intValue]; expiryDateComponents.hour = [[expiryTimeStr substringWithRange:NSMakeRange(8, 2)] intValue]; expiryDateComponents.minute = [[expiryTimeStr substringWithRange:NSMakeRange(10, 2)] intValue]; expiryDateComponents.second = [[expiryTimeStr substringWithRange:NSMakeRange(12, 2)] intValue]; NSCalendar *calendar = [NSCalendar currentCalendar]; expiryDate = [calendar dateFromComponents:expiryDateComponents]; [expiryDateComponents release]; } } } return expiryDate; } 

Solo necesitaba el nombre de la organización del emisor y la date de caducidad para mis propósitos, así que es todo el código que he incluido a continuación. Pero, en base a esto, debería ser capaz de averiguar el rest leyendo el file del encabezado x509.h

Editar:

A continuación, le indicamos cómo get el certificate. No he puesto ninguna gestión de errores, etc. trustResult consultar trustResult , err , etc., por ejemplo.

 NSURLAuthenticationChallenge *challenge; SecTrustResultType trustResult; SecTrustRef trust = challenge.protectionSpace.serverTrust; OSStatus err = SecTrustEvaluate(trust, &trustResult); SecCertificateRef certificate = SecGetLeafCertificate(trust); // See Apple docs for implementation of SecGetLeafCertificate 

No creo que haya una API pública para hacer esto en iOS. En OSX hay una serie de API SecCertificate para separar la información X.509.

Tenías razón Michael, iOS no te dará la API para hacer un trabajo completo con los certificates X.509. Afortunadamente, le dará acceso a los datos de los certificates codificados reales ( ASN.1 ). Desde allí, puedes hacer tu propia deencoding (no es muy divertida) o delegarla en una biblioteca existente, como hiciste con OpenSSL .

Aquí está mi versión que usa el marco .NET. Está destinado a ser utilizado por los desarrolladores de MonoTouch (y también por los desarrolladores de MonoMac) que necesitan interoperar con SecCertificateRef dentro de sus aplicaciones.

 public void Show (SecCertificate sc) { // get the SecCertificate "raw", ie ASN.1 encoded, data byte[] data = sc.DerData.ToArray<byte> (); // the build the managed X509Certificate2 from it X509Certificate2 cer = new X509Certificate2 (data); // to get all properties / methods available in .NET (pretty exhaustive) Console.WriteLine ("SubjectName: {0}", cer.Subject); Console.WriteLine ("IssuerName: {0}", cer.Issuer); Console.WriteLine ("NotBefore: {0}", cer.NotBefore); Console.WriteLine ("NotAfter: {0}", cer.NotAfter); Console.WriteLine ("SerialNumber: {0}", cer.SerialNumber); // ... } 

Si por alguna razón desea hacer esto sin OpenSSL, puede usar las teclas de extracción de Apple. El primero extraerá (solo) el sujeto y el emisor (hay más kSecOIDX509 para la mayoría de las otras cosas, como las dates de caducidad) y los pasa a imprimir.

  +(NSString*)stringFromCerificateWithLongwindedDescription:(SecCertificateRef) certificateRef { if (certificateRef == NULL) return @""; CFStringRef commonNameRef; OSStatus status; if ((status=SecCertificateCopyCommonName(certificateRef, &commonNameRef)) != errSecSuccess) { NSLog(@"Could not extract name from cert: %@", SecCopyErrorMessageString(status, NULL)); return @"Unreadable cert"; }; CFStringRef summaryRef = SecCertificateCopySubjectSummary(certificateRef); if (summaryRef == NULL) summaryRef = CFRetain(commonNameRef); CFErrorRef error; const void *keys[] = { kSecOIDX509V1SubjectName, kSecOIDX509V1IssuerName }; const void *labels[] = { "Subject", "Issuer" }; CFArrayRef keySelection = CFArrayCreate(NULL, keys , sizeof(keys)/sizeof(keys[0]), &kCFTypeArrayCallBacks); CFDictionaryRef vals = SecCertificateCopyValues(certificateRef, keySelection,&error); NSMutableString *longDesc = [[NSMutableString alloc] init]; for(int i = 0; i < sizeof(keys)/sizeof(keys[0]); i++) { CFDictionaryRef dict = CFDictionaryGetValue(vals, keys[i]); CFArrayRef values = CFDictionaryGetValue(dict, kSecPropertyKeyValue); if (values == NULL) continue; [longDesc appendFormat:@"%s:%@\n\n", labels[i], [NSString stringFromDNwithSubjectName:values]]; } CFRelease(vals); CFRelease(summaryRef); CFRelease(commonNameRef); return longDesc; } 

La segunda function es una sobre todo tratar de extraer cualquier cosa sobre la que puedas ponerte tus mitones:

 +(NSString *)stringFromDNwithSubjectName:(CFArrayRef)array { NSMutableString * out = [[NSMutableString alloc] init]; const void *keys[] = { kSecOIDCommonName, kSecOIDEmailAddress, kSecOIDOrganizationalUnitName, kSecOIDOrganizationName, kSecOIDLocalityName, kSecOIDStateProvinceName, kSecOIDCountryName }; const void *labels[] = { "CN", "E", "OU", "O", "L", "S", "C", "E" }; for(int i = 0; i < NVOID(keys); i++) { for (CFIndex n = 0 ; n < CFArrayGetCount(array); n++) { CFDictionaryRef dict = CFArrayGetValueAtIndex(array, n); if (CFGetTypeID(dict) != CFDictionaryGetTypeID()) continue; CFTypeRef dictkey = CFDictionaryGetValue(dict, kSecPropertyKeyLabel); if (!CFEqual(dictkey, keys[i])) continue; CFStringRef str = (CFStringRef) CFDictionaryGetValue(dict, kSecPropertyKeyValue); [out appendFormat:@"%s=%@ ", labels[i], (__bridge NSString*)str]; } } return [NSString stringWithString:out]; } 

mejor use simplemente SecCertificateCopyCommonName para que CN compare con su nombre de host requerido.

Para su información, suponiendo que esté usando HTTPS, verificar el propio CN es inútil, porque el sistema operativo ya verifica que el nombre esté presente en el certificate. Es más probable que desee comprobar la key pública (para la fijación de keys), que puede get del object de confianza sin tocar el certificate directamente.

Si la key pública coincide con la key anterior, entonces el sitio es legítimo o alguien ha comprometido completamente el sitio.