Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SecCertificateRef: How to get the certificate information?

I have a certificate (SecCertificateRef), I can check if it's valid and I can extract a "summary" using 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?

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

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?)

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
like image 218
Marc Schlösser Avatar asked Jan 13 '12 12:01

Marc Schlösser


4 Answers

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:

#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 CertificateGetIssuerName and CertificateGetExpiryDate are as follows:

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.h header file.

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.

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
like image 141
Michael Melanson Avatar answered Nov 18 '22 14:11

Michael Melanson


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

like image 24
Allen Avatar answered Nov 18 '22 13:11

Allen


You were right Michael, iOS won't give you the API to do a full job on a X.509 certificates. Thankfully it will give 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.

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

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);
    // ...
}
like image 4
poupou Avatar answered Nov 18 '22 12:11

poupou


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.

     +(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];
}
like image 3
Dirk-Willem van Gulik Avatar answered Nov 18 '22 12:11

Dirk-Willem van Gulik