2012-01-13 16 views
25

Tengo un certificado (SecCertificateRef), puedo verificar si es válido y puedo extraer un "resumen" usando SecCertificateCopySubjectSummary.SecCertificateRef: ¿cómo obtener la información del certificado?

¿Qué es exactamente el "resumen"? No entiendo el término "Una cadena que contiene un resumen legible por humanos del contenido del certificado". en la documentación de Apple. Creo que significan el "CN" en el certificado, ¿correcto?

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

Quiero tener algo como esto y estoy especialmente centrado en el "CN" para compararlo con la URL que envié para evitar los ataques de hombre en el medio. (o cualquier mejores ideas?)

Eso es lo que yo 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 protected] 
     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 protected] 
     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:[email protected] 
      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 

Respuesta

23

No podía esperar la respuesta a la recompensa, así que encontré una solución yo mismo. Como otros han dicho, Security.framework no le da una forma de obtener esta información, por lo que necesita para pedir OpenSSL para analizar los datos del certificado para 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); 

Dónde 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:
       //             1111 
       // There are other formats (e.g. 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 fecha de vencimiento para mis propósitos, así que ese es todo el código que he incluido a continuación. Pero, en base a esto, debería poder averiguar el resto leyendo el archivo de encabezado x509.h.

Editar:

es cómo conseguir el certificado aquí. No puse ningún manejo de error, etc. Por ejemplo, querrá marcar trustResult, err, etc.

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

¿podría aclarar cómo obtuvo NSData necesario para crear el objeto X509? ¿Qué es "certificado"? NSData * certificateData = (NSData *) SecCertificateCopyData (certificado); – Maggie

+0

Oh, puedes obtenerlo del desafío de la siguiente manera: NSURLAuthenticationChallenge * challenge; SecTrustResultType trustResult; SecTrustRef trust = challenge.protectionSpace.serverTrust; OSStatus err = SecTrustEvaluate (trust, & trustResult); Certificado SecCertiicateRef = SecGetLeafCertificate (confianza); // Ver documentos de Apple para la implementación de SecGetLeafCertificate –

+0

Uf, eso es feo. Déjame poner eso en la respuesta original. –

2

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

0

mejor simplemente use SecCertificateCopyCommonName para obtener CN para comparar con su nombre de host requerido.

+1

Eso es solo OSX, la pregunta es sobre iOS. –

+0

tiene razón, no lo verificó en detalle. – Allen

+1

Claro, en un sentido muy restringido. SecCertificate.h tiene aproximadamente 4 métodos disponibles en iOS. –

2

Eras Michael derecha, iOS no le dará la API para hacer un completo trabajo en un certificados X.509. Afortunadamente, le dará acceso a los datos de certificado codificados reales (ASN.1). Desde allí puede hacer su propia decodificación (no es muy divertido) o delegarla en una biblioteca existente, como lo hizo con OpenSSL.

Aquí está mi versión que usa .NET framework. Es útil para ser utilizado por los desarrolladores de MonoTouch (y también por los desarrolladores de MonoMac) que necesiten interoperar con SecCertificateRef dentro de sus aplicaciones.

public void Show (SecCertificate sc) 
{ 
    // get the SecCertificate "raw", i.e. 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); 
    // ... 
} 
+1

(suspiro feliz ...) ¿No es encantador usar C# para el desarrollo de iOS? Probé Xamarin en diciembre de 2014, pero aunque es fantástico usar C#, descubrí que el Xamarin Studio para Mac es demasiado inestable para recomendarlo a mi empresa. Al mirar este código, estoy empezando a extrañarlo ... –

1

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 fechas de caducidad) y los pasará para su impresión.

 +(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 función es una en la parte superior tratar de extraer todo lo que puede conseguir sus mitones en:

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

SecCertificateCopyValues ​​está disponible en iOS? – Freedom

+4

Me temo que solo está disponible en OSX – anavarroma

+3

Esto se aplica solo a OS X, por lo que no responde la pregunta. – Sulthan

0

FYI, asumiendo que usted está utilizando HTTPS, el control de la CN a sí mismo es sobre todo inútil, porque el sistema operativo ya verifica para asegurarse de que el nombre esté presente en el certificado. Es más probable que desee verificar la clave pública (para la fijación de clave), que puede obtener desde el objeto de confianza sin tocar el certificado directamente.

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

Cuestiones relacionadas