ios SecCertificateRef:如何获取证书信息?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8850524/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 16:16:53  来源:igfitidea点击:

SecCertificateRef: How to get the certificate information?

iphoneiosssl-certificatex509

提问by Marc Schl?sser

I have a certificate (SecCertificateRef), I can check if it's valid and I can extract a "summary" using SecCertificateCopySubjectSummary.

我有一个证书 (SecCertificateRef),我可以检查它是否有效,我可以使用 SecCertificateCopySubjectSummary 提取“摘要”。

What is the "summary" exactly? I don't understand the term "A string that contains a human-readable summary of the contents of the certificate." in the Apple documentation. I think, they mean the "CN" in the certificate, correct?

究竟什么是“总结”?我不理解术语“包含人类可读的证书内容摘要的字符串”。在 Apple 文档中。我想,他们的意思是证书中的“CN”,对吗?

Is there any method to get the clear X509-information out of SecCertificateRef? Does a cast to a keychain-object help?

有什么方法可以从 SecCertificateRef 中获取清晰的 X509 信息?转换到钥匙串对象有帮助吗?

I want to have something like this and I am especially focussed on the "CN" to compare it with the URL I submitted to avoid man-in-the-middle attacks. (Or any better ideas?)

我想要这样的东西,我特别关注“CN”,将它与我提交的 URL 进行比较,以避免中间人攻击。(或者有什么更好的想法?)

That is what I want to have:

这就是我想要的:

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

回答by Michael Melanson

I couldn't wait for an answer to the bounty, so I found a solution myself. As others said, Security.framework doesn't give you a way to get this information, so you need to ask OpenSSL to parse the certificate data for you:

我迫不及待地等待赏金的答案,所以我自己找到了解决方案。正如其他人所说,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);

Where CertificateGetIssuerNameand CertificateGetExpiryDateare as follows:

地点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;
}

I only actually needed the issuer's organization name and the expiry date for my purposes, so that's all the code I've included below. But, based on this you should be able to figure out the rest by reading the x509.hheader file.

为了我的目的,我实际上只需要发行人的组织名称和到期日期,所以这就是我在下面包含的所有代码。但是,基于此,您应该能够通过阅读x509.h头文件来找出其余部分。

Edit:

编辑:

Here's how to get the certificate. I haven't put any error handling, etc. You'll want to check trustResult, err, etc., for example.

以下是获取证书的方法。我没有把任何错误处理,等等。你需要检查trustResulterr等等,例如。

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

回答by Allen

better just use SecCertificateCopyCommonName to get CN to compare to your required hostname.

最好只使用 SecCertificateCopyCommonName 来让 CN 与您所需的主机名进行比较。

回答by poupou

You were right Michael, iOS won't give you the API to do a fulljob on a X.509 certificates. Thankfully it willgive you access to the actual (ASN.1) encoded certificate data. From there you can do your own decoding (not much fun) or delegate it to an existing library, like you did with OpenSSL.

你是对的迈克尔,iOS 不会给你 API 来完成X.509 证书的全部工作。幸运的是,它可以让您访问实际(ASN.1)编码的证书数据。从那里您可以进行自己的解码(不是很有趣)或将其委托给现有库,就像您对OpenSSL所做的那样。

Here's my version that uses the .NET framework. It's mean to be used by MonoTouchdevelopers (and MonoMac developers too) who needs to interoperate with SecCertificateRefwithin their applications.

这是我使用 .NET 框架的版本。它适合需要在其应用程序中进行互操作的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);
    // ...
}

回答by Dirk-Willem van Gulik

If for some reason you want to do this without OpenSSL one can use the apple extraction keys. The first one will extract (just) the Subject and Issuer (there are more kSecOIDX509's for most other things, like expiry dates) and pass them for printing.

如果出于某种原因您想在没有 OpenSSL 的情况下执行此操作,则可以使用苹果提取密钥。第一个将(仅)提取主题和发行者(对于大多数其他事物,例如到期日期,还有更多 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;
}

The second function is an over the top try to extract anything you can get your mittens on:

第二个功能是尝试提取任何您可以戴上手套的东西:

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

回答by Joshua Weinberg

I don't believe there is a public API to do this on iOS. On OSX there are a number of SecCertificateAPIs to pick apart the X.509 information.

我不相信有公共 API 可以在 iOS 上执行此操作。在 OSX 上,有许多SecCertificateAPI 可以分离 X.509 信息。

回答by dgatwood

FYI, assuming you're using HTTPS, checking the CN yourself is mostly useless, because the OS already checks to make sure the name is present in the cert. You're more likely to want to check the public key (for key pinning), which you can get from the trust object without touching the certificate directly.

仅供参考,假设您使用的是 HTTPS,那么您自己检查 CN 几乎没有用,因为操作系统已经进行了检查以确保该名称存在于证书中。您更有可能想要检查公钥(用于密钥固定),您可以从信任对象中获取,而无需直接接触证书。

If the public key matches the previous key, then either the site is legit or somebody has thoroughly compromised the site.

如果公钥与先前的密钥匹配,则该站点是合法的,或者有人彻底破坏了该站点。