【问题标题】:SecCertificateRef: How to get the certificate information?SecCertificateRef:如何获取证书信息?
【发布时间】:2012-01-13 12:23:41
【问题描述】:

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

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

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

我想要这样的东西,我特别关注“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=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

【问题讨论】:

  • 由于没有人回答您关于“摘要”是什么的问题:摘要是您可以向用户显示的字符串,例如如果您的用户应从证书列表中选择一个证书。它包含的信息应该足以让用户识别特定证书,但没有定义确切的信息,它也可能因 iOS 版本而异。

标签: iphone ios ssl-certificate x509


【解决方案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);

其中CertificateGetIssuerNameCertificateGetExpiryDate如下:

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 头文件找出其余部分。

编辑:

这是获得证书的方法。我没有进行任何错误处理等。例如,您需要检查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

【讨论】:

  • 您能否澄清一下您是如何获得创建 X509 对象所需的 NSData 的?什么是“证书”? NSData *certificateData = (NSData *) SecCertificateCopyData(certificate);
  • 哦,你可以从挑战中得到,如下: NSURLAuthenticationChallenge *challenge; SecTrustResultType 信任结果; SecTrustRef trust = challenge.protectionSpace.serverTrust; OSStatus err = SecTrustEvaluate(trust, &trustResult); SecCertiicateRef 证书 = SecGetLeafCertificate(trust); // SecGetLeafCertificate 的实现参见 Apple 文档
  • 呃,这很难看。让我把它放在原来的答案中。
  • 嗨,我们在 iOS 中有 openSSL 库或框架吗?我试过了,但不明白,你是怎么链接的?
  • @Michael Melanson,我们如何在 swift 或 swit 2 中做到这一点
【解决方案2】:

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

【讨论】:

  • 这只是OSX,问题是关于iOS。
  • 当然,在非常有限的意义上。 SecCertificate.h 在 iOS 上提供了大约 4 种方法。
  • SecCertificateCopyCommonName 从 iOS 10.3 开始可用。
【解决方案3】:

你说得对,Michael,iOS 不会为你提供 API 来对 X.509 证书进行完整工作。值得庆幸的是,它让您可以访问实际的 (ASN.1) 编码证书数据。从那里您可以进行自己的解码(不是很有趣)或将其委托给现有库,就像您对 OpenSSL 所做的那样。

这是我使用 .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);
    // ...
}

【讨论】:

  • (快乐的叹息...)使用 C# 进行 iOS 开发是不是很可爱!我确实在 2014 年 12 月尝试过 Xamarin,但尽管使用 C# 非常棒,但我发现 Xamarin Studio for Mac 太不稳定,无法推荐给我的公司。看着这段代码,我开始怀念它了……
【解决方案4】:

如果出于某种原因您想在没有 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;
}

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

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

【讨论】:

  • SecCertificateCopyValues 在 iOS 上可用吗?
  • 恐怕只有OSX才有
  • 这仅适用于 OS X,因此无法回答问题。
【解决方案5】:

我认为 iOS 上没有公共 API 可以执行此操作。在 OSX 上,有许多 SecCertificate API 可用于分离 X.509 信息。

【讨论】:

    【解决方案6】:

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

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

    【讨论】:

      猜你喜欢
      • 2018-07-07
      • 2014-04-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-05
      • 2011-12-13
      • 2015-09-25
      相关资源
      最近更新 更多