--- /dev/null
+//
+// X509Certificate.m
+//
+// This class is in the public domain.
+// Originally created by Robbie Hanson on Mon Jan 26 2009.
+// Updated and maintained by Deusty Designs and the Mac development community.
+//
+// http://code.google.com/p/cocoaasyncsocket/
+//
+// This class is largely derived from Apple's sample code project: SSLSample.
+// This class does not extract every bit of available information, just the most common fields.
+
+#import "X509Certificate.h"
+#import "AsyncSocket.h"
+#import <Security/Security.h>
+
+#define UTC_TIME_STRLEN 13
+#define GENERALIZED_TIME_STRLEN 15
+
+
+@implementation X509Certificate
+
+// Standard app-level memory functions required by CDSA
+
+static void * appMalloc (uint32 size, void *allocRef)
+{
+ return malloc(size);
+}
+static void * appCalloc(uint32 num, uint32 size, void *allocRef)
+{
+ return calloc(num, size);
+}
+static void * appRealloc (void *ptr, uint32 size, void *allocRef)
+{
+ return realloc(ptr, size);
+}
+static void appFree (void *mem_ptr, void *allocRef)
+{
+ free(mem_ptr);
+}
+
+
+static const CSSM_API_MEMORY_FUNCS memFuncs = {
+ (CSSM_MALLOC)appMalloc,
+ (CSSM_FREE)appFree,
+ (CSSM_REALLOC)appRealloc,
+ (CSSM_CALLOC)appCalloc,
+ NULL
+};
+
+static const CSSM_VERSION vers = {2, 0};
+static const CSSM_GUID testGuid = { 0xFADE, 0, 0, { 1,2,3,4,5,6,7,0 }};
+
+static BOOL CSSMStartup()
+{
+ CSSM_RETURN crtn;
+ CSSM_PVC_MODE pvcPolicy = CSSM_PVC_NONE;
+
+ crtn = CSSM_Init (&vers,
+ CSSM_PRIVILEGE_SCOPE_NONE,
+ &testGuid,
+ CSSM_KEY_HIERARCHY_NONE,
+ &pvcPolicy,
+ NULL /* reserved */);
+
+ if(crtn != CSSM_OK)
+ {
+ cssmPerror("CSSM_Init", crtn);
+ return NO;
+ }
+ else
+ {
+ return YES;
+ }
+}
+
+static CSSM_CL_HANDLE CLStartup()
+{
+ CSSM_CL_HANDLE clHandle;
+ CSSM_RETURN crtn;
+
+ if(CSSMStartup() == NO)
+ {
+ return 0;
+ }
+
+ crtn = CSSM_ModuleLoad(&gGuidAppleX509CL,
+ CSSM_KEY_HIERARCHY_NONE,
+ NULL, // eventHandler
+ NULL); // AppNotifyCallbackCtx
+ if(crtn != CSSM_OK)
+ {
+ cssmPerror("CSSM_ModuleLoad", crtn);
+ return 0;
+ }
+
+ crtn = CSSM_ModuleAttach (&gGuidAppleX509CL,
+ &vers,
+ &memFuncs, // memFuncs
+ 0, // SubserviceID
+ CSSM_SERVICE_CL, // SubserviceFlags - Where is this used?
+ 0, // AttachFlags
+ CSSM_KEY_HIERARCHY_NONE,
+ NULL, // FunctionTable
+ 0, // NumFuncTable
+ NULL, // reserved
+ &clHandle);
+ if(crtn != CSSM_OK)
+ {
+ cssmPerror("CSSM_ModuleAttach", crtn);
+ return 0;
+ }
+
+ return clHandle;
+}
+
+static void CLShutdown(CSSM_CL_HANDLE clHandle)
+{
+ CSSM_RETURN crtn;
+
+ crtn = CSSM_ModuleDetach(clHandle);
+ if(crtn != CSSM_OK)
+ {
+ cssmPerror("CSSM_ModuleDetach", crtn);
+ }
+
+ crtn = CSSM_ModuleUnload(&gGuidAppleX509CL, NULL, NULL);
+ if(crtn != CSSM_OK)
+ {
+ cssmPerror("CSSM_ModuleUnload", crtn);
+ }
+}
+
+static BOOL CompareCSSMData(const CSSM_DATA *d1, const CSSM_DATA *d2)
+{
+ if(d1 == NULL || d2 == NULL)
+ {
+ return NO;
+ }
+ if(d1->Length != d2->Length)
+ {
+ return NO;
+ }
+
+ return memcmp(d1->Data, d2->Data, d1->Length) == 0;
+}
+
+static BOOL CompareOids(const CSSM_OID *oid1, const CSSM_OID *oid2)
+{
+ if(oid1 == NULL || oid2 == NULL)
+ {
+ return NO;
+ }
+ if(oid1->Length != oid2->Length)
+ {
+ return NO;
+ }
+
+ return memcmp(oid1->Data, oid2->Data, oid1->Length) == 0;
+}
+
+static NSString* KeyForOid(const CSSM_OID *oid)
+{
+ if(CompareOids(oid, &CSSMOID_CountryName))
+ {
+ return X509_COUNTRY;
+ }
+ if(CompareOids(oid, &CSSMOID_OrganizationName))
+ {
+ return X509_ORGANIZATION;
+ }
+ if(CompareOids(oid, &CSSMOID_LocalityName))
+ {
+ return X509_LOCALITY;
+ }
+ if(CompareOids(oid, &CSSMOID_OrganizationalUnitName))
+ {
+ return X509_ORANIZATIONAL_UNIT;
+ }
+ if(CompareOids(oid, &CSSMOID_CommonName))
+ {
+ return X509_COMMON_NAME;
+ }
+ if(CompareOids(oid, &CSSMOID_Surname))
+ {
+ return X509_SURNAME;
+ }
+ if(CompareOids(oid, &CSSMOID_Title))
+ {
+ return X509_TITLE;
+ }
+ if(CompareOids(oid, &CSSMOID_StateProvinceName))
+ {
+ return X509_STATE_PROVINCE;
+ }
+ if(CompareOids(oid, &CSSMOID_CollectiveStateProvinceName))
+ {
+ return X509_COLLECTIVE_STATE_PROVINCE;
+ }
+ if(CompareOids(oid, &CSSMOID_EmailAddress))
+ {
+ return X509_EMAIL_ADDRESS;
+ }
+ if(CompareOids(oid, &CSSMOID_StreetAddress))
+ {
+ return X509_STREET_ADDRESS;
+ }
+ if(CompareOids(oid, &CSSMOID_PostalCode))
+ {
+ return X509_POSTAL_CODE;
+ }
+
+ // Not every possible Oid is checked for.
+ // Feel free to add any you may need.
+ // They are listed in the Security Framework's aoisattr.h file.
+
+ return nil;
+}
+
+static NSString* DataToString(const CSSM_DATA *data, const CSSM_BER_TAG *type)
+{
+ NSStringEncoding encoding;
+ switch (*type)
+ {
+ case BER_TAG_PRINTABLE_STRING :
+ case BER_TAG_TELETEX_STRING :
+
+ encoding = NSISOLatin1StringEncoding;
+ break;
+
+ case BER_TAG_PKIX_BMP_STRING :
+ case BER_TAG_PKIX_UNIVERSAL_STRING :
+ case BER_TAG_PKIX_UTF8_STRING :
+
+ encoding = NSUTF8StringEncoding;
+ break;
+
+ default :
+ return nil;
+ }
+
+ NSString *result = [[NSString alloc] initWithBytes:data->Data
+ length:data->Length
+ encoding:encoding];
+ return [result autorelease];
+}
+
+static NSDate* TimeToDate(const char *str, unsigned len)
+{
+ BOOL isUTC;
+ unsigned i;
+ long year, month, day, hour, minute, second;
+
+ // Check for null or empty strings
+ if(str == NULL || len == 0)
+ {
+ return nil;
+ }
+
+ // Ignore NULL termination
+ if(str[len - 1] == '\0')
+ {
+ len--;
+ }
+
+ // Check for proper string length
+ if(len == UTC_TIME_STRLEN)
+ {
+ // 2-digit year, not Y2K compliant
+ isUTC = YES;
+ }
+ else if(len == GENERALIZED_TIME_STRLEN)
+ {
+ // 4-digit year
+ isUTC = NO;
+ }
+ else
+ {
+ // Unknown format
+ return nil;
+ }
+
+ // Check that all characters except last are digits
+ for(i = 0; i < (len - 1); i++)
+ {
+ if(!(isdigit(str[i])))
+ {
+ return nil;
+ }
+ }
+
+ // Check last character is a 'Z'
+ if(str[len - 1] != 'Z' )
+ {
+ return nil;
+ }
+
+ // Start parsing
+ i = 0;
+ char tmp[5];
+
+ // Year
+ if(isUTC)
+ {
+ tmp[0] = str[i++];
+ tmp[1] = str[i++];
+ tmp[2] = '\0';
+
+ year = strtol(tmp, NULL, 10);
+
+ // 2-digit year:
+ // 0 <= year < 50 : assume century 21
+ // 50 <= year < 70 : illegal per PKIX
+ // 70 < year <= 99 : assume century 20
+
+ if(year < 50)
+ {
+ year += 2000;
+ }
+ else if(year < 70)
+ {
+ return nil;
+ }
+ else
+ {
+ year += 1900;
+ }
+ }
+ else
+ {
+ tmp[0] = str[i++];
+ tmp[1] = str[i++];
+ tmp[2] = str[i++];
+ tmp[3] = str[i++];
+ tmp[4] = '\0';
+
+ year = strtol(tmp, NULL, 10);
+ }
+
+ // Month
+ tmp[0] = str[i++];
+ tmp[1] = str[i++];
+ tmp[2] = '\0';
+
+ month = strtol(tmp, NULL, 10);
+
+ // Months are represented in format from 1 to 12
+ if(month > 12 || month <= 0)
+ {
+ return nil;
+ }
+
+ // Day
+ tmp[0] = str[i++];
+ tmp[1] = str[i++];
+ tmp[2] = '\0';
+
+ day = strtol(tmp, NULL, 10);
+
+ // Days are represented in format from 1 to 31
+ if(day > 31 || day <= 0)
+ {
+ return nil;
+ }
+
+ // Hour
+ tmp[0] = str[i++];
+ tmp[1] = str[i++];
+ tmp[2] = '\0';
+
+ hour = strtol(tmp, NULL, 10);
+
+ // Hours are represented in format from 0 to 23
+ if(hour > 23 || hour < 0)
+ {
+ return nil;
+ }
+
+ // Minute
+ tmp[0] = str[i++];
+ tmp[1] = str[i++];
+ tmp[2] = '\0';
+
+ minute = strtol(tmp, NULL, 10);
+
+ // Minutes are represented in format from 0 to 59
+ if(minute > 59 || minute < 0)
+ {
+ return nil;
+ }
+
+ // Second
+ tmp[0] = str[i++];
+ tmp[1] = str[i++];
+ tmp[2] = '\0';
+
+ second = strtol(tmp, NULL, 10);
+
+ // Seconds are represented in format from 0 to 59
+ if(second > 59 || second < 0)
+ {
+ return nil;
+ }
+
+ CFGregorianDate gDate = { year, month, day, hour, minute, second };
+ CFAbsoluteTime aTime = CFGregorianDateGetAbsoluteTime(gDate, NULL);
+
+ return [NSDate dateWithTimeIntervalSinceReferenceDate:aTime];
+}
+
+static NSData* RawToData(const CSSM_DATA *data)
+{
+ if(data == NULL)
+ {
+ return nil;
+ }
+
+ return [NSData dataWithBytes:data->Data length:data->Length];
+}
+
+static NSDictionary* X509NameToDictionary(const CSSM_X509_NAME *x509Name)
+{
+ if(x509Name == NULL)
+ {
+ return nil;
+ }
+
+ NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:6];
+ NSMutableArray *others = [NSMutableArray arrayWithCapacity:6];
+
+ UInt32 i, j;
+ for(i = 0; i < x509Name->numberOfRDNs; i++)
+ {
+ const CSSM_X509_RDN *name = &x509Name->RelativeDistinguishedName[i];
+
+ for(j = 0; j < name->numberOfPairs; j++)
+ {
+ const CSSM_X509_TYPE_VALUE_PAIR *pair = &name->AttributeTypeAndValue[j];
+
+ NSString *value = DataToString(&pair->value, &pair->valueType);
+ if(value)
+ {
+ NSString *key = KeyForOid(&pair->type);
+ if(key)
+ [result setObject:value forKey:key];
+ else
+ [others addObject:value];
+ }
+ }
+ }
+
+ if([others count] > 0)
+ {
+ [result setObject:others forKey:X509_OTHERS];
+ }
+
+ return result;
+}
+
+static void AddCSSMField(const CSSM_FIELD *field, NSMutableDictionary *dict)
+{
+ const CSSM_DATA *fieldData = &field->FieldValue;
+ const CSSM_OID *fieldOid = &field->FieldOid;
+
+ if(CompareOids(fieldOid, &CSSMOID_X509V1SerialNumber))
+ {
+ NSData *data = RawToData(fieldData);
+ if(data)
+ {
+ [dict setObject:data forKey:X509_SERIAL_NUMBER];
+ }
+ }
+ else if(CompareOids(fieldOid, &CSSMOID_X509V1IssuerNameCStruct))
+ {
+ CSSM_X509_NAME_PTR issuer = (CSSM_X509_NAME_PTR)fieldData->Data;
+ if(issuer && fieldData->Length == sizeof(CSSM_X509_NAME))
+ {
+ NSDictionary *issuerDict = X509NameToDictionary(issuer);
+ if(issuerDict)
+ {
+ [dict setObject:issuerDict forKey:X509_ISSUER];
+ }
+ }
+ }
+ else if(CompareOids(fieldOid, &CSSMOID_X509V1SubjectNameCStruct))
+ {
+ CSSM_X509_NAME_PTR subject = (CSSM_X509_NAME_PTR)fieldData->Data;
+ if(subject && fieldData->Length == sizeof(CSSM_X509_NAME))
+ {
+ NSDictionary *subjectDict = X509NameToDictionary(subject);
+ if(subjectDict)
+ {
+ [dict setObject:subjectDict forKey:X509_SUBJECT];
+ }
+ }
+ }
+ else if(CompareOids(fieldOid, &CSSMOID_X509V1ValidityNotBefore))
+ {
+ CSSM_X509_TIME_PTR time = (CSSM_X509_TIME_PTR)fieldData->Data;
+ if(time && fieldData->Length == sizeof(CSSM_X509_TIME))
+ {
+ NSDate *date = TimeToDate((const char *)time->time.Data, time->time.Length);
+ if(date)
+ {
+ [dict setObject:date forKey:X509_NOT_VALID_BEFORE];
+ }
+ }
+ }
+ else if(CompareOids(fieldOid, &CSSMOID_X509V1ValidityNotAfter))
+ {
+ CSSM_X509_TIME_PTR time = (CSSM_X509_TIME_PTR)fieldData->Data;
+ if(time && fieldData->Length == sizeof(CSSM_X509_TIME))
+ {
+ NSDate *date = TimeToDate((const char *)time->time.Data, time->time.Length);
+ if(date)
+ {
+ [dict setObject:date forKey:X509_NOT_VALID_AFTER];
+ }
+ }
+ }
+ else if(CompareOids(fieldOid, &CSSMOID_X509V1SubjectPublicKeyCStruct))
+ {
+ CSSM_X509_SUBJECT_PUBLIC_KEY_INFO_PTR pubKeyInfo = (CSSM_X509_SUBJECT_PUBLIC_KEY_INFO_PTR)fieldData->Data;
+ if(pubKeyInfo && fieldData->Length == sizeof(CSSM_X509_SUBJECT_PUBLIC_KEY_INFO))
+ {
+ NSData *data = RawToData(&pubKeyInfo->subjectPublicKey);
+ if(data)
+ {
+ [dict setObject:data forKey:X509_PUBLIC_KEY];
+ }
+ }
+ }
+}
+
++ (NSDictionary *)extractCertDictFromAsyncSocket:(AsyncSocket *)socket
+{
+ if(socket == nil)
+ {
+ return nil;
+ }
+
+ return [self extractCertDictFromReadStream:[socket getCFReadStream]];
+}
+
++ (NSDictionary *)extractCertDictFromReadStream:(CFReadStreamRef)readStream
+{
+ if(readStream == NULL)
+ {
+ return nil;
+ }
+
+ NSDictionary *result = nil;
+
+ CFArrayRef certs = CFReadStreamCopyProperty(readStream, kCFStreamPropertySSLPeerCertificates);
+ if(certs && CFArrayGetCount(certs) > 0)
+ {
+ // The first cert in the chain is the subject cert
+ SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(certs, 0);
+
+ result = [self extractCertDictFromCert:cert];
+ }
+
+ if(certs) CFRelease(certs);
+
+ return result;
+}
+
++ (NSDictionary *)extractCertDictFromIdentity:(SecIdentityRef)identity
+{
+ if(identity == NULL)
+ {
+ return nil;
+ }
+
+ NSDictionary *result = nil;
+ SecCertificateRef cert = NULL;
+
+ OSStatus err = SecIdentityCopyCertificate(identity, &cert);
+ if(err)
+ {
+ cssmPerror("SecIdentityCopyCertificate", err);
+ return nil;
+ }
+ else
+ {
+ result = [self extractCertDictFromCert:cert];
+ }
+
+ if(cert) CFRelease(cert);
+
+ return result;
+}
+
++ (NSDictionary *)extractCertDictFromCert:(SecCertificateRef)cert
+{
+ CSSM_CL_HANDLE clHandle = CLStartup();
+ if(clHandle == 0)
+ {
+ return nil;
+ }
+
+ NSMutableDictionary *result = nil;
+
+ CSSM_DATA certData;
+ if(SecCertificateGetData(cert, &certData) == noErr)
+ {
+ uint32 i;
+ uint32 numFields;
+ CSSM_FIELD_PTR fieldPtr;
+
+ CSSM_RETURN crtn = CSSM_CL_CertGetAllFields(clHandle, &certData, &numFields, &fieldPtr);
+ if(crtn == CSSM_OK)
+ {
+ result = [NSMutableDictionary dictionaryWithCapacity:6];
+
+ for(i = 0; i < numFields; i++)
+ {
+ AddCSSMField(&fieldPtr[i], result);
+ }
+
+ CSSM_CL_FreeFields(clHandle, numFields, &fieldPtr);
+ }
+ }
+
+ CLShutdown(clHandle);
+
+ return result;
+}
+
+@end