Подтвердить что ты не робот

SecCertificateRef: Как получить информацию о сертификате?

У меня есть сертификат (SecCertificateRef), я могу проверить, действительно ли он, и я могу извлечь "резюме" с помощью SecCertificateCopySubjectSummary.

Что такое "сводка"? Я не понимаю термин "Строка, содержащая человеко-читаемое резюме содержимого сертификата". в документации Apple. Я думаю, они означают "CN" в сертификате, правильно?

Есть ли способ получить четкую информацию X509 из SecCertificateRef? Помогает ли приведение к объекту keychain-объекта?

Я хочу иметь что-то вроде этого, и я особенно сосредоточен на "CN", чтобы сравнить его с URL-адресом, который я отправил, чтобы избежать атак типа "человек в середине". (Или какие-нибудь лучшие идеи?)

Вот что я хочу:

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
4b9b3361

Ответ 1

Я не мог дождаться ответа на щедрость, поэтому сам нашел решение. Как и другие, Security.framework не дает вам способ получить эту информацию, поэтому вам нужно попросить OpenSSL проанализировать данные сертификата для вас:

#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);

Где CertificateGetIssuerName и CertificateGetExpiryDate следующие:

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 (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;
}

Мне на самом деле требовалось имя организации-эмитента и дата истечения срока действия для моих целей, так что весь код, который я включил ниже. Но, основываясь на этом, вы должны быть в состоянии выяснить остальное, прочитав заголовочный файл x509.h.

Edit:

Здесь, как получить сертификат. Я не обрабатывал обработку ошибок и т.д. Например, вы хотите проверить trustResult, err и т.д.

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

Ответ 2

Вы были правы Майкл, iOS не даст вам API, чтобы выполнить полную работу по сертификатам X.509. К счастью, предоставит вам доступ к фактическим (ASN.1) закодированным данным сертификата. Оттуда вы можете сделать свое собственное декодирование (не очень весело) или делегировать его в существующую библиотеку, например, с OpenSSL.

Здесь моя версия, использующая .NET framework. Это означает, что разработчики MonoTouch (и разработчики MonoMac тоже), которым необходимо взаимодействовать с SecCertificateRef в своих приложениях.

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);
    // ...
}

Ответ 3

Я не верю, что для iOS существует общедоступный API. В OSX есть несколько API SecCertificate, чтобы выделить информацию X.509.

Ответ 4

Если по какой-то причине вы хотите сделать это без OpenSSL, вы можете использовать клавиши извлечения Apple. Первый из них будет извлекать (только) Субъект и Эмитент (для большинства других вещей, таких как даты истечения срока действия, больше kSecOIDX509) и передавать их для печати. ​​

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

Вторая функция заключается в том, чтобы извлечь все, что вы можете получить в своих рукавицах:

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

Ответ 5

лучше просто использовать SecCertificateCopyCommonName, чтобы получить CN для сравнения с вашим требуемым именем хоста.

Ответ 6

FYI, предполагая, что вы используете HTTPS, проверка CN сама по себе бесполезна, потому что ОС уже проверяет, чтобы имя присутствовало в сертификате. Вы, скорее всего, захотите проверить открытый ключ (для закрепления ключа), который вы можете получить из объекта доверия, не касаясь сертификата напрямую.

Если открытый ключ соответствует предыдущему ключу, то либо сайт является законным, либо кто-то полностью скомпрометировал сайт.

Ответ 7

Я знаю, что не вопрос, как получить сертификат X509 из SecCertificateRef, но это была проблема, с которой я имел дело. После этого потока я нашел полезный способ экспорта файла.cer:

Сертификат SecCertificateRef = nil;

SecIdentityCopyCertificate (identityReference, & certificate);

NSData * certificateData = (NSData *) CFBridgingRelease (SecCertificateCopyData (сертификат));

[certificateData writeToFile: @"/tmp/X509.cer" atomically: YES];