SecCertificateRef:如何获取证书信息?

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

什么是“总结”? 我不理解术语“一个string,其中包含对证书内容的可读摘要”。 在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 

我不能等待赏金的答案,所以我自己find了解决办法。 正如其他人所说,Security.framework并没有给你一个获取这些信息的方法,所以你需要问OpenSSL为你parsing证书数据:

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

我实际上只需要发行人的组织名称和有效期限,以下是我所包含的所有代码。 但是,基于这个,你应该可以通过阅读x509.h头文件来找出其余的。

编辑:

以下是如何获得证书。 我没有把任何error handling,例如,你会想检查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 

我不相信在iOS上有这样的公共API。 在OSX上,有许多SecCertificate API来挑选X.509信息。

你是对的,迈克尔,iOS不会给你的API做一个完整的X.509证书工作。 谢天谢地,它会让您访问实际的( ASN.1 )编码的证书数据。 从那里你可以做你自己的解码(没有多less乐趣),或者把它委托给现有的库,就像你使用OpenSSL一样 。

这是我使用.NET框架的版本。 MonoTouch开发人员(以及MonoMac开发人员)也需要在其应用程序中与SecCertificateRef进行互操作。

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

如果由于某种原因你想在没有OpenSSL的情况下这样做,你可以使用苹果提取键。 第一个将提取(只)主体和发行人(有更多的kSecOIDX509的大多数其他东西,如到期date),并通过他们的打印。

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

第二个function是尝试提取任何可以获得连指手套的信息:

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

更好的是使用SecCertificateCopyCommonName来获取CN来比较你所需的主机名。

仅供参考,假设您使用HTTPS,检查CN本身是无用的,因为操作系统已经检查,以确保名称存在于证书中。 你更可能想检查公钥(用于密钥locking),你可以从信任对象中获得公钥,而不需要直接接触证书。

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