/* * Copyright (c) 2004 Apple Computer, Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include #include "CommonCode.h" #include "DotMacRelation.h" #include "CommonCrypto/CommonDigest.h" #include #include #include /* int mNumberOfValues; Value* mValues; BlobValue *mData; */ DotMacTuple::DotMacTuple (int numberOfValues) : mNumberOfValues (numberOfValues), mValues (NULL), mData (NULL) { mValues = new Value*[numberOfValues]; int i; for (i = 0; i < numberOfValues; ++i) { mValues[i] = NULL; } } DotMacTuple::~DotMacTuple () { // walk the value array and delete each value int i; for (i = 0; i < mNumberOfValues; ++i) { if (mValues[i] != NULL) { delete mValues[i]; } } delete [] mValues; if (mData != NULL) { delete mData; } } void DotMacTuple::SetValue (int i, Value* v) { mValues[i] = v; } Value* DotMacTuple::GetValue (int i) { return mValues[i]; } int DotMacTuple::GetNumberOfValues () { return mNumberOfValues; } void DotMacTuple::GetData (CSSM_DATA &data) { size_t t; const uint8* d = mData->GetRawValue (t); data.Data = (uint8*) d; data.Length = t; } void DotMacTuple::SetData (BlobValue *value) { mData = value; } DotMacUniqueIdentifier::DotMacUniqueIdentifier (DotMacTuple *t) : UniqueIdentifier (CSSM_DL_DB_RECORD_X509_CERTIFICATE), mTuple (t) { } DotMacUniqueIdentifier::~DotMacUniqueIdentifier () { delete mTuple; } void DotMacUniqueIdentifier::Export (CSSM_DB_UNIQUE_RECORD &record) { // memset the whole thing to 0 memset (&record, 0, sizeof (CSSM_DB_UNIQUE_RECORD)); record.RecordIdentifier.Data = (uint8*) this; } DotMacTuple* DotMacUniqueIdentifier::GetTuple () { return mTuple; } static void * appMalloc (uint32 size, void *allocRef) { return (malloc (size)); } static void appFree (void *mem_ptr, void *allocRef) { free (mem_ptr); return; } static void * appRealloc (void *ptr, uint32 size, void *allocRef) { return (realloc (ptr, size)); } static void * appCalloc (uint32 num, uint32 size, void *allocRef) { return (calloc (num, size)); } static CSSM_API_MEMORY_FUNCS memFuncs = { appMalloc, appFree, appRealloc, appCalloc, NULL }; static void CheckResult (CSSM_RETURN result) { if (result != 0) { CSSMError::ThrowCSSMError (result); } } void DotMacRelation::InitializeCertLibrary () { if (mCertificateLibrary != 0) { return; } // figure out which GUID to attach to const CSSM_GUID* attachGuid = &gGuidAppleX509CL; // Initialize CDSA CSSM_VERSION version = {2, 0}; // load the CL CSSM_RETURN result = CSSM_ModuleLoad (attachGuid, CSSM_KEY_HIERARCHY_NONE, NULL, NULL); CheckResult (result); result = CSSM_ModuleAttach (attachGuid, &version, &memFuncs, 0, // no subservice ID CSSM_SERVICE_CL, 0, CSSM_KEY_HIERARCHY_NONE, NULL, 0, NULL, &mCertificateLibrary); CheckResult (result); } DotMacRelation::DotMacRelation () : PartialRelation (CSSM_DL_DB_RECORD_X509_CERTIFICATE, kNumberOfX509Attributes), mCertificateLibrary (0) { SetColumnNames ("CertType", "CertEncoding", "PrintName", "Alias", "Subject", "Issuer", "SerialNumber", "SubjectKeyIdentifier", "PublicKeyHash"); SetColumnIDs ('ctyp', 'cenc', 'labl', 'alis', 'subj', 'issu', 'snbr', 'skid', 'hpky'); SetColumnFormats (CSSM_DB_ATTRIBUTE_FORMAT_UINT32, CSSM_DB_ATTRIBUTE_FORMAT_UINT32, CSSM_DB_ATTRIBUTE_FORMAT_BLOB, CSSM_DB_ATTRIBUTE_FORMAT_BLOB, CSSM_DB_ATTRIBUTE_FORMAT_BLOB, CSSM_DB_ATTRIBUTE_FORMAT_BLOB, CSSM_DB_ATTRIBUTE_FORMAT_BLOB, CSSM_DB_ATTRIBUTE_FORMAT_BLOB, CSSM_DB_ATTRIBUTE_FORMAT_BLOB); } DotMacRelation::~DotMacRelation () { if (mCertificateLibrary != 0) { CSSM_ModuleDetach (mCertificateLibrary); } } Query* DotMacRelation::MakeQuery (const CSSM_QUERY* query) { return new DotMacQuery (this, query); } Tuple* DotMacRelation::GetTupleFromUniqueIdentifier (UniqueIdentifier* uniqueID) { DotMacUniqueIdentifier *id = (DotMacUniqueIdentifier*) uniqueID; return id->GetTuple (); } UniqueIdentifier* DotMacRelation::ImportUniqueIdentifier (CSSM_DB_UNIQUE_RECORD *uniqueRecord) { CSSMError::ThrowCSSMError (CSSMERR_DL_UNSUPPORTED_QUERY); } CSSM_CL_HANDLE DotMacRelation::GetCLHandle () { InitializeCertLibrary (); return mCertificateLibrary; } /* SPI to specify timeout on CFReadStream */ #define _kCFStreamPropertyReadTimeout CFSTR("_kCFStreamPropertyReadTimeout") /* the timeout we set */ #define READ_STREAM_TIMEOUT 15 const int kResponseIncrement = 4096; char* DotMacQuery::ReadStream (CFURLRef url, size_t &responseLength) { SInt32 ito; CFNumberRef cfnTo = NULL; // make a connection to the provided URL CFHTTPMessageRef httpRequestRef = CFHTTPMessageCreateRequest (kCFAllocatorDefault, CFSTR("GET"), url, kCFHTTPVersion1_1); if (httpRequestRef == NULL) { CSSMError::ThrowCSSMError (CSSMERR_DL_RECORD_NOT_FOUND); } // open the stream CFReadStreamRef httpStreamRef = CFReadStreamCreateForHTTPRequest (kCFAllocatorDefault, httpRequestRef); if (httpStreamRef == NULL) { CSSMError::ThrowCSSMError (CSSMERR_DL_RECORD_NOT_FOUND); } // set a reasonable timeout ito = READ_STREAM_TIMEOUT; cfnTo = CFNumberCreate(NULL, kCFNumberSInt32Type, &ito); if(!CFReadStreamSetProperty(httpStreamRef, _kCFStreamPropertyReadTimeout, cfnTo)) { // oh well - keep going } if (CFReadStreamOpen (httpStreamRef) == false) { CFRelease(httpRequestRef); CFRelease(httpStreamRef); CFRelease(cfnTo); CSSMError::ThrowCSSMError (CSSMERR_DL_RECORD_NOT_FOUND); } char* response = (char*) malloc (kResponseIncrement); size_t responseBufferLength = kResponseIncrement; responseLength = 0; // read data from the stream CFIndex bytesRead = CFReadStreamRead (httpStreamRef, (UInt8*) response + responseLength, kResponseIncrement); while (bytesRead > 0) { responseLength += bytesRead; responseBufferLength = responseLength + kResponseIncrement; response = (char*) realloc (response, responseBufferLength); bytesRead = CFReadStreamRead (httpStreamRef, (UInt8*) response + responseLength, kResponseIncrement); } CFRelease (httpRequestRef); CFRelease (httpStreamRef); CFRelease(cfnTo); // check for error if (bytesRead < 0) { CSSMError::ThrowCSSMError (CSSMERR_DL_RECORD_NOT_FOUND); } return response; } std::string DotMacQuery::ReadLine () { // extract one line from the buffer char* lineStart = mBufferPos; while (mBufferPos < mTarget && *mBufferPos != '\n') { mBufferPos += 1; } if (mBufferPos >= mTarget) { CSSMError::ThrowCSSMError (CSSMERR_DL_DATABASE_CORRUPT); } // calculate the end of line char* end = mBufferPos; if (*(end - 1) == '\r') { end -= 1; } // tweek if we have to if (end < lineStart) { end = lineStart; } // move our cursor to the next line for the next time through the loop mBufferPos += 1; // the line of text is now delimited by (lineStart, end) size_t length = end - lineStart; return std::string (lineStart, length); } static u_int8_t gBase64Array[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; static void Base64ToBin (const std::string &s, u_int8_t* data, size_t &length) { int16_t accum = 0; int bitsInAccum = 0; u_int8_t* finger = data; int numEquals = 0; unsigned i; for (i = 0; i < s.length (); ++i) { int index = s[i]; int b64; if (index == '=') { b64 = 0; numEquals += 1; } else { b64 = gBase64Array[index]; } accum = (accum << 6) | b64; bitsInAccum += 6; if (bitsInAccum >= 8) { bitsInAccum -= 8; *finger++ = (accum >> bitsInAccum) & 0xFF; } } // adjust for padding finger -= numEquals; length = finger - data; } void DotMacQuery::ReadCertificatesFromURL (CFURLRef url) { // get our data mBuffer = ReadStream (url, mBufferSize); mBufferPos = mBuffer; mTarget = mBuffer + mBufferSize; std::string userName; while (mBufferPos < mTarget) { userName = ReadLine (); if (userName.length () == 0) { // skip blank lines continue; } else { break; } } if (mBufferPos >= mTarget) // out of data so soon? { CSSMError::ThrowCSSMError (CSSMERR_DL_ENDOFDATA); } // parse the data while (mBufferPos < mTarget) { // the next line should be all dashes, and can be ignored std::string line = ReadLine (); if (mBufferPos >= mTarget) { // we are done goto Exit; } line = ReadLine (); // the next line should include the magic words BEGIN CERTIFICATE if (line.find ("BEGIN CERTIFICATE", 0) == std::string::npos) { CSSMError::ThrowCSSMError (CSSMERR_DL_DATABASE_CORRUPT); } // what follows is the certificate data, make a big string that concatenates it together void* certData = NULL; size_t certLen = 0; line = ReadLine (); while (mBufferPos < mTarget && line.find ("END CERTIFICATE", 0) == std::string::npos) { u_int8_t dataBuffer [line.length ()]; // without question big enough to hold the data size_t length = 0; Base64ToBin (line, dataBuffer, length); certData = realloc (certData, certLen + length); memmove (((char*) certData) + certLen, dataBuffer, length); certLen += length; line = ReadLine (); } if (mBufferPos >= mTarget) { CSSMError::ThrowCSSMError (CSSMERR_DL_DATABASE_CORRUPT); } CSSM_DATA cert; cert.Data = (uint8*) certData; cert.Length = certLen; // save off the cert mCertList.push_back (cert); } Exit: mCertIterator = mCertList.begin (); } const char* kEmailName = "Alias"; const char* kPrintName = "PrintName"; const char* kMacDotCom = "@mac.com"; static bool StringEndsWith (const std::string &s, const char* suffix) { unsigned suffixLength = strlen (suffix); if (suffixLength > s.length ()) { return false; } return strncmp (suffix, ((char*) s.c_str ()) + s.length () - suffixLength, suffixLength) == 0; } DotMacQuery::DotMacQuery (DotMacRelation* relation, const CSSM_QUERY *queryBase) : Query (relation, queryBase) { uint32 i; bool found = false; CSSM_DATA name; // look for a selection predicate we'e comfortable with for (i = 0; i < mNumSelectionPredicates; ++i) { // the name has to be "Alias" or "PrintName", specified in string format if (mSelectionPredicates[i].GetAttributeNameFormat () != CSSM_DB_ATTRIBUTE_NAME_AS_STRING) { continue; } char *attrName = mSelectionPredicates[i].GetAttributeName(); if(strcmp(attrName, kPrintName) && strcmp(attrName, kEmailName)) { continue; } if (found) { // oops, we can only have one "Alias" or "PrintName" predicate in the query CSSMError::ThrowCSSMError (CSSMERR_DL_UNSUPPORTED_QUERY); } // the operator has to be CSSM_DB_EQUAL or CSSM_DB_CONTAINS. We // treat these identically. switch(mSelectionPredicates[i].GetOperator ()) { case CSSM_DB_EQUAL: case CSSM_DB_CONTAINS: break; default: CSSMError::ThrowCSSMError (CSSMERR_DL_UNSUPPORTED_QUERY); } // we have at found a predicate of the proper form. name = mSelectionPredicates[i].GetValue (0); found = true; } if (!found) { CSSMError::ThrowCSSMError (CSSMERR_DL_UNSUPPORTED_QUERY); } // get the name of the entity. It had better end with "@mac.com". std::string nameAsString ((char*) name.Data, name.Length); if (!StringEndsWith (nameAsString, kMacDotCom)) { CSSMError::ThrowCSSMError (CSSMERR_DL_UNSUPPORTED_QUERY); } CFStringRef userName = CFStringCreateWithBytes (kCFAllocatorDefault, (UInt8*) name.Data, name.Length - strlen (kMacDotCom), kCFStringEncodingMacRoman, false); // now that we've policed the query, make the query URL CFMutableStringRef queryString = CFStringCreateMutable (kCFAllocatorDefault, 0); // set to the beginning of the query CFStringAppendCString (queryString, "http://certinfo.mac.com/lookup?", kCFStringEncodingMacRoman); // append the user name CFStringAppend (queryString, userName); CFRelease (userName); // make the URL object that corresponds to our string CFURLRef url = CFURLCreateWithString (kCFAllocatorDefault, queryString, NULL); CFRelease (queryString); secdebug ("dotmacdl", "reading certs for %s", nameAsString.c_str ()); ReadCertificatesFromURL (url); CFRelease (url); } DotMacQuery::~DotMacQuery () { CertList::iterator it = mCertList.begin (); while (it != mCertList.end ()) { free (it++->Data); } } static bool CompareOIDs (const CSSM_OID &a, const CSSM_OID &b) { if (a.Length != b.Length) { return false; } return memcmp (a.Data, b.Data, a.Length) == 0; } static CSSM_DATA GetValueFromFields (CSSM_FIELD *fields, uint32 numFields, const CSSM_OID& oid) { uint32 i; for (i = 0; i < numFields; ++i) { if (CompareOIDs (fields[i].FieldOid, oid)) { return fields[i].FieldValue; } } CSSMError::ThrowCSSMError(CSSMERR_CSSM_INVALID_ATTRIBUTE); } static CSSM_DATA* GetAttributeFromX509Name (CSSM_X509_NAME *name, const CSSM_OID& oid) { uint32 i; for (i = 0; i < name->numberOfRDNs; ++i) { CSSM_X509_RDN &rdn = name->RelativeDistinguishedName[i]; uint32 j; for (j = 0; j < rdn.numberOfPairs; ++j) { CSSM_X509_TYPE_VALUE_PAIR &pair = rdn.AttributeTypeAndValue[j]; if (CompareOIDs (pair.type, oid)) { return &pair.value; } } } return NULL; } Tuple* DotMacQuery::GetNextTuple (UniqueIdentifier *&id) { CSSM_CL_HANDLE clHandle = ((DotMacRelation*) mRelation)->GetCLHandle (); while (mCertIterator != mCertList.end ()) // no more certs for this query? { // we now need to parse the cert CSSM_DATA cert = *mCertIterator++; CSSM_FIELD *fields; uint32 numberOfFields; CSSM_RETURN result = CSSM_CL_CertGetAllFields (clHandle, &cert, &numberOfFields, &fields); CheckResult (result); // get the version CSSM_DATA data = GetValueFromFields (fields, numberOfFields, CSSMOID_X509V1Version); // make a tuple DotMacTuple* t = new DotMacTuple (kNumberOfX509Attributes); // set the data types uint32 certType = data.Data[0]; t->SetValue (kCertTypeID, new UInt32Value (certType)); t->SetValue (kCertEncodingID, new UInt32Value (CSSM_CERT_ENCODING_DER)); // we need the print name, so start with the subject name data = GetValueFromFields (fields, numberOfFields, CSSMOID_X509V1SubjectNameCStruct); // from there, get the attribute CSSM_X509_NAME* namePtr = (CSSM_X509_NAME*) data.Data; CSSM_DATA *dp; dp = GetAttributeFromX509Name (namePtr, CSSMOID_CommonName); std::string commonName = std::string ((char*) dp->Data, dp->Length); secdebug ("dotmacdl", "Common name=%s", commonName.c_str ()); // does the common name end in kMacDotCom? If not, add it if (!StringEndsWith (commonName, kMacDotCom)) { commonName += kMacDotCom; } t->SetValue (kCertPrintName, dp == NULL ? NULL : new BlobValue (dp->Data, dp->Length)); // also get the email address to act as the alias dp = GetAttributeFromX509Name (namePtr, CSSMOID_EmailAddress); if (dp == NULL) { secdebug ("dotmacdl", "CSSMOID_EmailAddress is NULL; using commonName"); t->SetValue (kCertAlias, new BlobValue ((UInt8*) commonName.c_str (), commonName.length ())); } else { std::string s = std::string ((char*) dp->Data, dp->Length); if (!StringEndsWith (s, kMacDotCom)) { s += kMacDotCom; } secdebug ("dotmacdl", s.c_str ()); t->SetValue (kCertAlias, dp == NULL ? NULL : new BlobValue (dp->Data, dp->Length)); } // get the subject data = GetValueFromFields (fields, numberOfFields, CSSMOID_X509V1SubjectName); t->SetValue (kCertSubject, new BlobValue (data.Data, data.Length)); // get the issuer data = GetValueFromFields (fields, numberOfFields, CSSMOID_X509V1IssuerName); t->SetValue (kCertIssuer, new BlobValue (data.Data, data.Length)); // get the serial number data = GetValueFromFields (fields, numberOfFields, CSSMOID_X509V1SerialNumber); t->SetValue (kCertSerialNumber, new BlobValue (data.Data, data.Length)); // handle the subject key identifier t->SetValue (kCertSubjectKeyIdentifier, NULL); // make hash of the public key. data = GetValueFromFields (fields, numberOfFields, CSSMOID_X509V1SubjectPublicKeyCStruct); CSSM_X509_SUBJECT_PUBLIC_KEY_INFO *publicKeyInfo = (CSSM_X509_SUBJECT_PUBLIC_KEY_INFO *) data.Data; CC_SHA1_CTX context; CC_SHA1_Init (&context); CC_SHA1_Update (&context, publicKeyInfo->subjectPublicKey.Data, publicKeyInfo->subjectPublicKey.Length); uint8 sha1Digest [20]; CC_SHA1_Final (sha1Digest, &context); t->SetValue (kCertPublicKeyHash, new BlobValue (sha1Digest, 20)); // get the data that we will ultimately return t->SetData (new BlobValue (cert.Data, cert.Length)); // release the cert data CSSM_CL_FreeFields (clHandle, numberOfFields, &fields); if (EvaluateTuple (t)) { id = new DotMacUniqueIdentifier (t); return t; } else { delete t; } } // out of data CSSMError::ThrowCSSMError (CSSMERR_DL_ENDOFDATA); }