/* * Copyright (c) 2002-2007,2012 Apple 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@ */ // // Certificate.cpp // #include #include #include #include #include #include #include #include #include #include #include #include using namespace KeychainCore; CL Certificate::clForType(CSSM_CERT_TYPE type) { return CL(gGuidAppleX509CL); } Certificate::Certificate(const CSSM_DATA &data, CSSM_CERT_TYPE type, CSSM_CERT_ENCODING encoding) : ItemImpl(CSSM_DL_DB_RECORD_X509_CERTIFICATE, reinterpret_cast(NULL), UInt32(data.Length), reinterpret_cast(data.Data)), mHaveTypeAndEncoding(true), mPopulated(false), mType(type), mEncoding(encoding), mCL(clForType(type)), mCertHandle(0), mV1SubjectPublicKeyCStructValue(NULL), mV1SubjectNameCStructValue(NULL), mV1IssuerNameCStructValue(NULL), mSha1Hash(NULL) { if (data.Length == 0 || data.Data == NULL) MacOSError::throwMe(errSecParam); } // db item constructor Certificate::Certificate(const Keychain &keychain, const PrimaryKey &primaryKey, const CssmClient::DbUniqueRecord &uniqueId) : ItemImpl(keychain, primaryKey, uniqueId), mHaveTypeAndEncoding(false), mPopulated(false), mCL(NULL), mCertHandle(0), mV1SubjectPublicKeyCStructValue(NULL), mV1SubjectNameCStructValue(NULL), mV1IssuerNameCStructValue(NULL), mSha1Hash(NULL) { } Certificate* Certificate::make(const Keychain &keychain, const PrimaryKey &primaryKey, const CssmClient::DbUniqueRecord &uniqueId) { Certificate* c = new Certificate(keychain, primaryKey, uniqueId); keychain->addItem(primaryKey, c); return c; } Certificate* Certificate::make(const Keychain &keychain, const PrimaryKey &primaryKey) { Certificate* c = new Certificate(keychain, primaryKey); keychain->addItem(primaryKey, c); return c; } // PrimaryKey item constructor Certificate::Certificate(const Keychain &keychain, const PrimaryKey &primaryKey) : ItemImpl(keychain, primaryKey), mHaveTypeAndEncoding(false), mPopulated(false), mCL(NULL), mCertHandle(0), mV1SubjectPublicKeyCStructValue(NULL), mV1SubjectNameCStructValue(NULL), mV1IssuerNameCStructValue(NULL), mSha1Hash(NULL) { // @@@ In this case we don't know the type... } Certificate::Certificate(Certificate &certificate) : ItemImpl(certificate), mHaveTypeAndEncoding(certificate.mHaveTypeAndEncoding), mPopulated(false /* certificate.mPopulated */), mType(certificate.mType), mEncoding(certificate.mEncoding), mCL(certificate.mCL), mCertHandle(0), mV1SubjectPublicKeyCStructValue(NULL), mV1SubjectNameCStructValue(NULL), mV1IssuerNameCStructValue(NULL), mSha1Hash(NULL) { } Certificate::~Certificate() try { if (mV1SubjectPublicKeyCStructValue) releaseFieldValue(CSSMOID_X509V1SubjectPublicKeyCStruct, mV1SubjectPublicKeyCStructValue); if (mCertHandle && mCL) CSSM_CL_CertAbortCache(mCL->handle(), mCertHandle); if (mV1SubjectNameCStructValue) releaseFieldValue(CSSMOID_X509V1SubjectNameCStruct, mV1SubjectNameCStructValue); if (mV1IssuerNameCStructValue) releaseFieldValue(CSSMOID_X509V1IssuerNameCStruct, mV1IssuerNameCStructValue); if (mSha1Hash) CFRelease(mSha1Hash); } catch (...) { } CSSM_HANDLE Certificate::certHandle() { StLock_(mMutex); const CSSM_DATA *cert = &data(); if (!mCertHandle) { if (CSSM_RETURN retval = CSSM_CL_CertCache(clHandle(), cert, &mCertHandle)) CssmError::throwMe(retval); } return mCertHandle; } /* Return a zero terminated list of CSSM_DATA_PTR's with the values of the field specified by field. Caller must call releaseFieldValues to free the storage allocated by this call. */ CSSM_DATA_PTR * Certificate::copyFieldValues(const CSSM_OID &field) { StLock_(mMutex); CSSM_CL_HANDLE clh = clHandle(); CSSM_DATA_PTR fieldValue, *fieldValues; CSSM_HANDLE resultsHandle = 0; uint32 numberOfFields = 0; CSSM_RETURN result; result = CSSM_CL_CertGetFirstCachedFieldValue(clh, certHandle(), &field, &resultsHandle, &numberOfFields, &fieldValue); if (result) { if (result == CSSMERR_CL_NO_FIELD_VALUES) return NULL; CssmError::throwMe(result); } fieldValues = new CSSM_DATA_PTR[numberOfFields + 1]; fieldValues[0] = fieldValue; fieldValues[numberOfFields] = NULL; for (uint32 value = 1; value < numberOfFields; ++value) { CSSM_RETURN cresult = CSSM_CL_CertGetNextCachedFieldValue(clh, resultsHandle, &fieldValues[value]); if (cresult) { fieldValues[value] = NULL; result = cresult; break; // No point in continuing really. } } CSSM_CL_CertAbortQuery(clh, resultsHandle); if (result) { releaseFieldValues(field, fieldValues); CssmError::throwMe(result); } return fieldValues; } void Certificate::releaseFieldValues(const CSSM_OID &field, CSSM_DATA_PTR *fieldValues) { StLock_(mMutex); if (fieldValues) { CSSM_CL_HANDLE clh = clHandle(); for (int ix = 0; fieldValues[ix]; ++ix) CSSM_CL_FreeFieldValue(clh, &field, fieldValues[ix]); delete[] fieldValues; } } void Certificate::addParsedAttribute(const CSSM_DB_ATTRIBUTE_INFO &info, const CSSM_OID &field) { StLock_(mMutex); CSSM_DATA_PTR *fieldValues = copyFieldValues(field); if (fieldValues) { CssmDbAttributeData &anAttr = mDbAttributes->add(info); for (int ix = 0; fieldValues[ix]; ++ix) anAttr.add(*fieldValues[ix], *mDbAttributes); releaseFieldValues(field, fieldValues); } } void Certificate::addSubjectKeyIdentifier() { StLock_(mMutex); const CSSM_DB_ATTRIBUTE_INFO &info = Schema::attributeInfo(kSecSubjectKeyIdentifierItemAttr); const CSSM_OID &field = CSSMOID_SubjectKeyIdentifier; CSSM_DATA_PTR *fieldValues = copyFieldValues(field); if (fieldValues) { CssmDbAttributeData &anAttr = mDbAttributes->add(info); for (int ix = 0; fieldValues[ix]; ++ix) { const CSSM_X509_EXTENSION *extension = reinterpret_cast(fieldValues[ix]->Data); if (extension == NULL || fieldValues[ix]->Length != sizeof(CSSM_X509_EXTENSION)) { assert(extension != NULL && fieldValues[ix]->Length == sizeof(CSSM_X509_EXTENSION)); continue; } const CE_SubjectKeyID *skid = reinterpret_cast(extension->value.parsedValue); if (skid == NULL) { assert(skid != NULL); continue; } anAttr.add(*skid, *mDbAttributes); } releaseFieldValues(field, fieldValues); } } /* Return a CSSM_DATA_PTR with the value of the first field specified by field. Caller must call releaseFieldValue to free the storage allocated by this call. */ CSSM_DATA_PTR Certificate::copyFirstFieldValue(const CSSM_OID &field) { StLock_(mMutex); CSSM_CL_HANDLE clh = clHandle(); CSSM_DATA_PTR fieldValue; CSSM_HANDLE resultsHandle = 0; uint32 numberOfFields = 0; CSSM_RETURN result; result = CSSM_CL_CertGetFirstCachedFieldValue(clh, certHandle(), &field, &resultsHandle, &numberOfFields, &fieldValue); if (result) { if (result == CSSMERR_CL_NO_FIELD_VALUES) return NULL; CssmError::throwMe(result); } result = CSSM_CL_CertAbortQuery(clh, resultsHandle); if (result) { releaseFieldValue(field, fieldValue); CssmError::throwMe(result); } return fieldValue; } void Certificate::releaseFieldValue(const CSSM_OID &field, CSSM_DATA_PTR fieldValue) { StLock_(mMutex); if (fieldValue) { CSSM_CL_HANDLE clh = clHandle(); CSSM_CL_FreeFieldValue(clh, &field, fieldValue); } } /* This method computes the keyIdentifier for the public key in the cert as described below: The keyIdentifier is composed of the 160-bit SHA-1 hash of the value of the BIT STRING subjectPublicKey (excluding the tag, length, and number of unused bits). */ const CssmData & Certificate::publicKeyHash() { StLock_(mMutex); if (mPublicKeyHash.Length) return mPublicKeyHash; CSSM_DATA_PTR keyPtr = copyFirstFieldValue(CSSMOID_CSSMKeyStruct); if (keyPtr && keyPtr->Data) { CssmClient::CSP csp(gGuidAppleCSP); CssmClient::PassThrough passThrough(csp); CSSM_KEY *key = reinterpret_cast(keyPtr->Data); void *outData; CssmData *cssmData; /* Given a CSSM_KEY_PTR in any format, obtain the SHA-1 hash of the * associated key blob. * Key is specified in CSSM_CSP_CreatePassThroughContext. * Hash is allocated by the CSP, in the App's memory, and returned * in *outData. */ passThrough.key(key); passThrough(CSSM_APPLECSP_KEYDIGEST, NULL, &outData); cssmData = reinterpret_cast(outData); assert(cssmData->Length <= sizeof(mPublicKeyHashBytes)); mPublicKeyHash.Data = mPublicKeyHashBytes; mPublicKeyHash.Length = cssmData->Length; memcpy(mPublicKeyHash.Data, cssmData->Data, cssmData->Length); csp.allocator().free(cssmData->Data); csp.allocator().free(cssmData); } releaseFieldValue(CSSMOID_CSSMKeyStruct, keyPtr); return mPublicKeyHash; } const CssmData & Certificate::subjectKeyIdentifier() { StLock_(mMutex); if (mSubjectKeyID.Length) return mSubjectKeyID; CSSM_DATA_PTR fieldValue = copyFirstFieldValue(CSSMOID_SubjectKeyIdentifier); if (fieldValue && fieldValue->Data && fieldValue->Length == sizeof(CSSM_X509_EXTENSION)) { const CSSM_X509_EXTENSION *extension = reinterpret_cast(fieldValue->Data); const CE_SubjectKeyID *skid = reinterpret_cast(extension->value.parsedValue); // CSSM_DATA if (skid->Length <= sizeof(mSubjectKeyIDBytes)) { mSubjectKeyID.Data = mSubjectKeyIDBytes; mSubjectKeyID.Length = skid->Length; memcpy(mSubjectKeyID.Data, skid->Data, skid->Length); } else mSubjectKeyID.Length = 0; } releaseFieldValue(CSSMOID_SubjectKeyIdentifier, fieldValue); return mSubjectKeyID; } /* * Given an CSSM_X509_NAME, Find the first (or last) name/value pair with * a printable value which matches the specified OID (e.g., CSSMOID_CommonName). * Returns the CFString-style encoding associated with name component's BER tag. * Returns NULL if none found. */ static const CSSM_DATA * findPrintableField( const CSSM_X509_NAME &x509Name, const CSSM_OID *tvpType, // NULL means "any printable field" bool lastInstance, // false means return first instance CFStringBuiltInEncodings *encoding) // RETURNED { const CSSM_DATA *result = NULL; for(uint32 rdnDex=0; rdnDexnumberOfPairs; tvpDex++) { const CSSM_X509_TYPE_VALUE_PAIR *tvpPtr = &rdnPtr->AttributeTypeAndValue[tvpDex]; /* type/value pair: match caller's specified type? */ if(tvpType != NULL && tvpType->Data != NULL) { if(tvpPtr->type.Length != tvpType->Length) { continue; } if(memcmp(tvpPtr->type.Data, tvpType->Data, tvpType->Length)) { /* If we don't have a match but the requested OID is CSSMOID_UserID, * look for a matching X.500 UserID OID: (0.9.2342.19200300.100.1.1) */ const char cssm_userid_oid[] = { 0x09,0x49,0x86,0x49,0x1f,0x12,0x8c,0xe4,0x81,0x81 }; const char x500_userid_oid[] = { 0x09,0x92,0x26,0x89,0x93,0xF2,0x2C,0x64,0x01,0x01 }; if(!(tvpType->Length == sizeof(cssm_userid_oid) && !memcmp(tvpPtr->type.Data, x500_userid_oid, sizeof(x500_userid_oid)) && !memcmp(tvpType->Data, cssm_userid_oid, sizeof(cssm_userid_oid)))) { continue; } } } /* printable? */ switch(tvpPtr->valueType) { case BER_TAG_PRINTABLE_STRING: case BER_TAG_IA5_STRING: *encoding = kCFStringEncodingASCII; result = &tvpPtr->value; break; case BER_TAG_PKIX_UTF8_STRING: case BER_TAG_GENERAL_STRING: case BER_TAG_PKIX_UNIVERSAL_STRING: *encoding = kCFStringEncodingUTF8; result = &tvpPtr->value; break; case BER_TAG_T61_STRING: case BER_TAG_VIDEOTEX_STRING: case BER_TAG_ISO646_STRING: *encoding = kCFStringEncodingISOLatin1; result = &tvpPtr->value; break; case BER_TAG_PKIX_BMP_STRING: *encoding = kCFStringEncodingUnicode; result = &tvpPtr->value; break; default: /* not printable */ break; } /* if we found a result and we want the first instance, return it now. */ if(result && !lastInstance) { return result; } } /* for each pair */ } /* for each RDN */ /* result is NULL if no printable component was found */ return result; } /* * Infer printable label for a given CSSM_X509_NAME. Returns NULL * if no appropriate printable name found. Returns the CFString-style * encoding associated with name component's BER tag. Also optionally * returns Description component and its encoding if present and the * returned name component was one we explicitly requested. */ static const CSSM_DATA *inferLabelFromX509Name( const CSSM_X509_NAME *x509Name, CFStringBuiltInEncodings *encoding, // RETURNED const CSSM_DATA **description, // optionally RETURNED CFStringBuiltInEncodings *descrEncoding) // RETURNED if description != NULL { const CSSM_DATA *printValue; if(description != NULL) { *description = findPrintableField(*x509Name, &CSSMOID_Description, false, descrEncoding); } /* * Search order (take the first one found with a printable * value): * -- common name * -- Organizational Unit * -- Organization * -- email address * -- field of any kind */ printValue = findPrintableField(*x509Name, &CSSMOID_CommonName, true, encoding); if(printValue != NULL) { return printValue; } printValue = findPrintableField(*x509Name, &CSSMOID_OrganizationalUnitName, false, encoding); if(printValue != NULL) { return printValue; } printValue = findPrintableField(*x509Name, &CSSMOID_OrganizationName, false, encoding); if(printValue != NULL) { return printValue; } printValue = findPrintableField(*x509Name, &CSSMOID_EmailAddress, false, encoding); if(printValue != NULL) { return printValue; } /* if we didn't get one of the above names, don't append description */ if(description != NULL) { *description = NULL; } /* take anything */ return findPrintableField(*x509Name, NULL, false, encoding); } /* * Infer printable label for a given an CSSM_X509_NAME. Returns NULL * if no appropriate printable name found. */ const CSSM_DATA *SecInferLabelFromX509Name( const CSSM_X509_NAME *x509Name) { /* callees of this routine don't care about the encoding */ CFStringBuiltInEncodings encoding = kCFStringEncodingASCII; return inferLabelFromX509Name(x509Name, &encoding, NULL, &encoding); } void Certificate::inferLabel(bool addLabel, CFStringRef *rtnString) { StLock_(mMutex); // Set PrintName and optionally the Alias attribute for this certificate, based on the // X509 SubjectAltName and SubjectName. const CSSM_DATA *printName = NULL; const CSSM_DATA *description = NULL; std::vector emailAddresses; CSSM_DATA puntData; CssmAutoData printPlusDescr(Allocator::standard()); CssmData printPlusDescData; CFStringBuiltInEncodings printEncoding = kCFStringEncodingUTF8; CFStringBuiltInEncodings descrEncoding = kCFStringEncodingUTF8; // Find the SubjectAltName fields, if any, and extract all the GNT_RFC822Name entries from all of them const CSSM_OID &sanOid = CSSMOID_SubjectAltName; CSSM_DATA_PTR *sanValues = copyFieldValues(sanOid); const CSSM_OID &snOid = CSSMOID_X509V1SubjectNameCStruct; CSSM_DATA_PTR snValue = copyFirstFieldValue(snOid); getNames(sanValues, snValue, GNT_RFC822Name, emailAddresses); if (snValue && snValue->Data) { const CSSM_X509_NAME &x509Name = *(const CSSM_X509_NAME *)snValue->Data; printName = inferLabelFromX509Name(&x509Name, &printEncoding, &description, &descrEncoding); if (printName) { /* Don't ever use "Thawte Freemail Member" as the label for a cert. Instead force a fall back on the email address. */ const char tfm[] = "Thawte Freemail Member"; if ( (printName->Length == sizeof(tfm) - 1) && !memcmp(printName->Data, tfm, sizeof(tfm) - 1)) { printName = NULL; } } } /* Do a check to see if a '\0' was at the end of printName and strip it. */ CssmData cleanedUpPrintName; if((printName != NULL) && (printName->Length != 0) && (printEncoding != kCFStringEncodingISOLatin1) && (printEncoding != kCFStringEncodingUnicode) && (printName->Data[printName->Length - 1] == '\0')) { cleanedUpPrintName.Data = printName->Data; cleanedUpPrintName.Length = printName->Length - 1; printName = &cleanedUpPrintName; } if((printName != NULL) && (description != NULL) && (description->Length != 0)) { /* * Munge Print Name (which in this case is the CommonName) and Description * together with the Description in parentheses. We convert from whatever * format Print Name and Description are in to UTF8 here. */ CFRef combo(CFStringCreateMutable(NULL, 0)); CFRef cfPrint(CFStringCreateWithBytes(NULL, printName->Data, (CFIndex)printName->Length, printEncoding, true)); CssmData cleanedUpDescr(description->Data, description->Length); if ((cleanedUpDescr.Data[cleanedUpDescr.Length - 1] == '\0') && (descrEncoding != kCFStringEncodingISOLatin1) && (descrEncoding != kCFStringEncodingUnicode)) { cleanedUpDescr.Length--; } CFRef cfDesc(CFStringCreateWithBytes(NULL, cleanedUpDescr.Data, (CFIndex)cleanedUpDescr.Length, descrEncoding, true)); CFStringAppend(combo, cfPrint); CFStringAppendCString(combo, " (", kCFStringEncodingASCII); CFStringAppend(combo, cfDesc); CFStringAppendCString(combo, ")", kCFStringEncodingASCII); CFRef comboData(CFStringCreateExternalRepresentation(NULL, combo, kCFStringEncodingUTF8, 0)); printPlusDescr.copy(CFDataGetBytePtr(comboData), CFDataGetLength(comboData)); printPlusDescData = printPlusDescr; printName = &printPlusDescData; printEncoding = kCFStringEncodingUTF8; } if (printName == NULL) { /* If the we couldn't find a label use the emailAddress instead. */ if (!emailAddresses.empty()) printName = &emailAddresses[0]; else { /* punt! */ puntData.Data = (uint8 *)"X509 Certificate"; puntData.Length = 16; printName = &puntData; } printEncoding = kCFStringEncodingUTF8; } /* If we couldn't find an email address just use the printName which might be the url or something else useful. */ if (emailAddresses.empty()) emailAddresses.push_back(CssmData::overlay(*printName)); /* What do we do with the inferred label - return it or add it mDbAttributes? */ if (addLabel) { mDbAttributes->add(Schema::kX509CertificatePrintName, *printName); CssmDbAttributeData &attrData = mDbAttributes->add(Schema::kX509CertificateAlias); /* Add the email addresses to attrData and normalize them. */ uint32 ix = 0; for (std::vector::const_iterator it = emailAddresses.begin(); it != emailAddresses.end(); ++it, ++ix) { /* Add the email address using the allocator from mDbAttributes. */ attrData.add(*it, *mDbAttributes); /* Normalize the emailAddresses in place since attrData already copied it. */ normalizeEmailAddress(attrData.Value[ix]); } } if (rtnString) { CFStringBuiltInEncodings testEncoding = printEncoding; if(testEncoding == kCFStringEncodingISOLatin1) { // try UTF-8 first testEncoding = kCFStringEncodingUTF8; } *rtnString = CFStringCreateWithBytes(NULL, printName->Data, (CFIndex)printName->Length, testEncoding, true); if(*rtnString == NULL && printEncoding == kCFStringEncodingISOLatin1) { // string cannot be represented in UTF-8, fall back to ISO Latin 1 *rtnString = CFStringCreateWithBytes(NULL, printName->Data, (CFIndex)printName->Length, printEncoding, true); } } // Clean up if (snValue) releaseFieldValue(snOid, snValue); if (sanValues) releaseFieldValues(sanOid, sanValues); } void Certificate::populateAttributes() { StLock_(mMutex); if (mPopulated) return; addParsedAttribute(Schema::attributeInfo(kSecSubjectItemAttr), CSSMOID_X509V1SubjectName); addParsedAttribute(Schema::attributeInfo(kSecIssuerItemAttr), CSSMOID_X509V1IssuerName); addParsedAttribute(Schema::attributeInfo(kSecSerialNumberItemAttr), CSSMOID_X509V1SerialNumber); addSubjectKeyIdentifier(); if(!mHaveTypeAndEncoding) MacOSError::throwMe(errSecDataNotAvailable); // @@@ Or some other error. // Adjust mType based on the actual version of the cert. CSSM_DATA_PTR versionPtr = copyFirstFieldValue(CSSMOID_X509V1Version); if (versionPtr && versionPtr->Data && versionPtr->Length == sizeof(uint32)) { mType = CSSM_CERT_X_509v1 + (*reinterpret_cast(versionPtr->Data)); } else mType = CSSM_CERT_X_509v1; releaseFieldValue(CSSMOID_X509V1Version, versionPtr); mDbAttributes->add(Schema::attributeInfo(kSecCertTypeItemAttr), mType); mDbAttributes->add(Schema::attributeInfo(kSecCertEncodingItemAttr), mEncoding); mDbAttributes->add(Schema::attributeInfo(kSecPublicKeyHashItemAttr), publicKeyHash()); inferLabel(true); mPopulated = true; } const CssmData & Certificate::data() { StLock_(mMutex); CssmDataContainer *data = mData.get(); if (!data && mKeychain) { // Make sure mUniqueId is set. dbUniqueRecord(); CssmDataContainer _data; mData = NULL; /* new data allocated by CSPDL, implicitly freed by CssmDataContainer */ mUniqueId->get(NULL, &_data); /* this saves a copy to be freed at destruction and to be passed to caller */ setData((UInt32)_data.length(), _data.data()); return *mData.get(); } // If the data hasn't been set we can't return it. if (!data) MacOSError::throwMe(errSecDataNotAvailable); return *data; } CFHashCode Certificate::hash() { (void)data(); // ensure that mData is set up return ItemImpl::hash(); } CSSM_CERT_TYPE Certificate::type() { StLock_(mMutex); if (!mHaveTypeAndEncoding) { SecKeychainAttribute attr; attr.tag = kSecCertTypeItemAttr; attr.data = &mType; attr.length = sizeof(mType); getAttribute(attr, NULL); } return mType; } CSSM_CERT_ENCODING Certificate::encoding() { StLock_(mMutex); if (!mHaveTypeAndEncoding) { SecKeychainAttribute attr; attr.tag = kSecCertEncodingItemAttr; attr.data = &mEncoding; attr.length = sizeof(mEncoding); getAttribute(attr, NULL); } return mEncoding; } const CSSM_X509_ALGORITHM_IDENTIFIER_PTR Certificate::algorithmID() { StLock_(mMutex); if (!mV1SubjectPublicKeyCStructValue) mV1SubjectPublicKeyCStructValue = copyFirstFieldValue(CSSMOID_X509V1SubjectPublicKeyCStruct); CSSM_X509_SUBJECT_PUBLIC_KEY_INFO *info = (CSSM_X509_SUBJECT_PUBLIC_KEY_INFO *)mV1SubjectPublicKeyCStructValue->Data; CSSM_X509_ALGORITHM_IDENTIFIER *algid = &info->algorithm; return algid; } CFDataRef Certificate::sha1Hash() { StLock_(mMutex); if (!mSha1Hash) { SecCertificateRef certRef = handle(false); CFAllocatorRef allocRef = (certRef) ? CFGetAllocator(certRef) : NULL; CSSM_DATA certData = data(); if (certData.Length == 0 || !certData.Data) { MacOSError::throwMe(errSecDataNotAvailable); } const UInt8 *dataPtr = (const UInt8 *)certData.Data; CFIndex dataLen = (CFIndex)certData.Length; CFMutableDataRef digest = CFDataCreateMutable(allocRef, CC_SHA1_DIGEST_LENGTH); CFDataSetLength(digest, CC_SHA1_DIGEST_LENGTH); CCDigest(kCCDigestSHA1, dataPtr, dataLen, CFDataGetMutableBytePtr(digest)); mSha1Hash = digest; } return mSha1Hash; /* object is owned by our instance; caller should NOT release it */ } CFStringRef Certificate::commonName() { StLock_(mMutex); return distinguishedName(&CSSMOID_X509V1SubjectNameCStruct, &CSSMOID_CommonName); } CFStringRef Certificate::distinguishedName(const CSSM_OID *sourceOid, const CSSM_OID *componentOid) { StLock_(mMutex); CFStringRef rtnString = NULL; CSSM_DATA_PTR fieldValue = copyFirstFieldValue(*sourceOid); CSSM_X509_NAME_PTR x509Name = (CSSM_X509_NAME_PTR)fieldValue->Data; const CSSM_DATA *printValue = NULL; CFStringBuiltInEncodings encoding; if (fieldValue && fieldValue->Data) printValue = findPrintableField(*x509Name, componentOid, true, &encoding); if (printValue) rtnString = CFStringCreateWithBytes(NULL, printValue->Data, CFIndex(printValue->Length), encoding, true); releaseFieldValue(*sourceOid, fieldValue); return rtnString; } /* * Return a CFString containing the first email addresses for this certificate, based on the * X509 SubjectAltName and SubjectName. */ CFStringRef Certificate::copyFirstEmailAddress() { StLock_(mMutex); CFStringRef rtnString; const CSSM_OID &sanOid = CSSMOID_SubjectAltName; CSSM_DATA_PTR *sanValues = copyFieldValues(sanOid); const CSSM_OID &snOid = CSSMOID_X509V1SubjectNameCStruct; CSSM_DATA_PTR snValue = copyFirstFieldValue(snOid); std::vector emailAddresses; getNames(sanValues, snValue, GNT_RFC822Name, emailAddresses); if (emailAddresses.empty()) rtnString = NULL; else { /* Encoding is kCFStringEncodingUTF8 since the string is either PRINTABLE_STRING, IA5_STRING, T61_STRING or PKIX_UTF8_STRING. */ rtnString = CFStringCreateWithBytes(NULL, emailAddresses[0].Data, (CFIndex)emailAddresses[0].Length, kCFStringEncodingUTF8, true); } // Clean up if (snValue) releaseFieldValue(snOid, snValue); if (sanValues) releaseFieldValues(sanOid, sanValues); return rtnString; } /* * Return a CFArray containing the DNS hostnames for this certificate, based on the * X509 SubjectAltName and SubjectName. */ CFArrayRef Certificate::copyDNSNames() { StLock_(mMutex); CFMutableArrayRef array = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); std::vector dnsNames; // Find the SubjectAltName fields, if any, and extract the GNT_DNSName entries from all of them const CSSM_OID &sanOid = CSSMOID_SubjectAltName; CSSM_DATA_PTR *sanValues = copyFieldValues(sanOid); const CSSM_OID &snOid = CSSMOID_X509V1SubjectNameCStruct; CSSM_DATA_PTR snValue = copyFirstFieldValue(snOid); getNames(sanValues, snValue, GNT_DNSName, dnsNames); for (std::vector::const_iterator it = dnsNames.begin(); it != dnsNames.end(); ++it) { /* Encoding is kCFStringEncodingUTF8 since the string is either PRINTABLE_STRING, IA5_STRING, T61_STRING or PKIX_UTF8_STRING. */ CFStringRef string = CFStringCreateWithBytes(NULL, it->Data, static_cast(it->Length), kCFStringEncodingUTF8, true); /* Be prepared for improperly formatted (non-UTF8) strings! */ if (!string) continue; CFArrayAppendValue(array, string); CFRelease(string); } // Clean up if (snValue) releaseFieldValue(snOid, snValue); if (sanValues) releaseFieldValues(sanOid, sanValues); return array; } /* * Return a CFArray containing the email addresses for this certificate, based on the * X509 SubjectAltName and SubjectName. */ CFArrayRef Certificate::copyEmailAddresses() { StLock_(mMutex); CFMutableArrayRef array = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); std::vector emailAddresses; // Find the SubjectAltName fields, if any, and extract all the GNT_RFC822Name entries from all of them const CSSM_OID &sanOid = CSSMOID_SubjectAltName; CSSM_DATA_PTR *sanValues = copyFieldValues(sanOid); const CSSM_OID &snOid = CSSMOID_X509V1SubjectNameCStruct; CSSM_DATA_PTR snValue = copyFirstFieldValue(snOid); getNames(sanValues, snValue, GNT_RFC822Name, emailAddresses); for (std::vector::const_iterator it = emailAddresses.begin(); it != emailAddresses.end(); ++it) { /* Encoding is kCFStringEncodingUTF8 since the string is either PRINTABLE_STRING, IA5_STRING, T61_STRING or PKIX_UTF8_STRING. */ CFStringRef string = CFStringCreateWithBytes(NULL, it->Data, static_cast(it->Length), kCFStringEncodingUTF8, true); /* Be prepared for improperly formatted (non-UTF8) strings! */ if (!string) continue; CFArrayAppendValue(array, string); CFRelease(string); } // Clean up if (snValue) releaseFieldValue(snOid, snValue); if (sanValues) releaseFieldValues(sanOid, sanValues); return array; } const CSSM_X509_NAME_PTR Certificate::subjectName() { StLock_(mMutex); if (!mV1SubjectNameCStructValue) if ((mV1SubjectNameCStructValue = copyFirstFieldValue(CSSMOID_X509V1SubjectNameCStruct)) == NULL) return NULL; return (const CSSM_X509_NAME_PTR)mV1SubjectNameCStructValue->Data; } const CSSM_X509_NAME_PTR Certificate::issuerName() { StLock_(mMutex); if (!mV1IssuerNameCStructValue) if ((mV1IssuerNameCStructValue = copyFirstFieldValue(CSSMOID_X509V1IssuerNameCStruct)) == NULL) return NULL; return (const CSSM_X509_NAME_PTR)mV1IssuerNameCStructValue->Data; } CSSM_CL_HANDLE Certificate::clHandle() { StLock_(mMutex); if (!mCL) mCL = clForType(type()); return mCL->handle(); } bool Certificate::operator < (Certificate &other) { // Certificates in different keychains are considered equal if data is equal // Note that the Identity '<' operator relies on this assumption. return data() < other.data(); } bool Certificate::operator == (Certificate &other) { // Certificates in different keychains are considered equal if data is equal // Note that the Identity '==' operator relies on this assumption. return data() == other.data(); } void Certificate::update() { ItemImpl::update(); } Item Certificate::copyTo(const Keychain &keychain, Access *newAccess) { StLock_(mMutex); /* Certs can't have access controls. */ if (newAccess) MacOSError::throwMe(errSecNoAccessForItem); Item item(new Certificate(data(), type(), encoding())); keychain->add(item); return item; } void Certificate::didModify() { } PrimaryKey Certificate::add(Keychain &keychain) { StLock_(mMutex); // If we already have a Keychain we can't be added. if (mKeychain) MacOSError::throwMe(errSecDuplicateItem); populateAttributes(); CSSM_DB_RECORDTYPE recordType = mDbAttributes->recordType(); Db db(keychain->database()); // add the item to the (regular) db try { mUniqueId = db->insert(recordType, mDbAttributes.get(), mData.get()); } catch (const CssmError &e) { if (e.osStatus() != CSSMERR_DL_INVALID_RECORDTYPE) throw; // Create the cert relation and try again. db->createRelation(CSSM_DL_DB_RECORD_X509_CERTIFICATE, "CSSM_DL_DB_RECORD_X509_CERTIFICATE", Schema::X509CertificateSchemaAttributeCount, Schema::X509CertificateSchemaAttributeList, Schema::X509CertificateSchemaIndexCount, Schema::X509CertificateSchemaIndexList); keychain->keychainSchema()->didCreateRelation( CSSM_DL_DB_RECORD_X509_CERTIFICATE, "CSSM_DL_DB_RECORD_X509_CERTIFICATE", Schema::X509CertificateSchemaAttributeCount, Schema::X509CertificateSchemaAttributeList, Schema::X509CertificateSchemaIndexCount, Schema::X509CertificateSchemaIndexList); mUniqueId = db->insert(recordType, mDbAttributes.get(), mData.get()); } mPrimaryKey = keychain->makePrimaryKey(recordType, mUniqueId); mKeychain = keychain; return mPrimaryKey; } SecPointer Certificate::publicKey() { StLock_(mMutex); SecPointer keyItem; // Return a CSSM_DATA_PTR with the value of the first field specified by field. // Caller must call releaseFieldValue to free the storage allocated by this call. // call OSStatus SecKeyGetCSSMKey(SecKeyRef key, const CSSM_KEY **cssmKey); to retrieve CSSM_DATA_PTR keyPtr = copyFirstFieldValue(CSSMOID_CSSMKeyStruct); if (keyPtr && keyPtr->Data) { CssmClient::CSP csp(gGuidAppleCSP); CssmKey *cssmKey = reinterpret_cast(keyPtr->Data); CssmClient::Key key(csp, *cssmKey); keyItem = new KeyItem(key); // Clear out KeyData since KeyItem() takes over ownership of the key, and we don't want it getting released. cssmKey->KeyData.Data = NULL; cssmKey->KeyData.Length = 0; } releaseFieldValue(CSSMOID_CSSMKeyStruct, keyPtr); return keyItem; } // This function "borrowed" from the X509 CL, which is (currently) linked into // the Security.framework as a built-in plugin. extern "C" bool getField_normRDN_NSS ( const CSSM_DATA &derName, uint32 &numFields, // RETURNED (if successful, 0 or 1) CssmOwnedData &fieldValue); // RETURNED KCCursor Certificate::cursorForIssuerAndSN(const StorageManager::KeychainList &keychains, const CssmData &issuer, const CssmData &serialNumber) { CssmAutoData fieldValue(Allocator::standard(Allocator::normal)); uint32 numFields; // We need to decode issuer, normalize it, then re-encode it if (!getField_normRDN_NSS(issuer, numFields, fieldValue)) MacOSError::throwMe(errSecDataNotAvailable); // Code basically copied from SecKeychainSearchCreateFromAttributes and SecKeychainSearchCopyNext: KCCursor cursor(keychains, kSecCertificateItemClass, NULL); cursor->conjunctive(CSSM_DB_AND); cursor->add(CSSM_DB_EQUAL, Schema::kX509CertificateIssuer, fieldValue.get()); cursor->add(CSSM_DB_EQUAL, Schema::kX509CertificateSerialNumber, serialNumber); return cursor; } KCCursor Certificate::cursorForIssuerAndSN_CF(const StorageManager::KeychainList &keychains, CFDataRef issuer, CFDataRef serialNumber) { // This assumes a normalized issuer CSSM_DATA issuerCSSM, serialNumberCSSM; issuerCSSM.Length = CFDataGetLength(issuer); issuerCSSM.Data = const_cast(CFDataGetBytePtr(issuer)); serialNumberCSSM.Length = CFDataGetLength(serialNumber); serialNumberCSSM.Data = const_cast(CFDataGetBytePtr(serialNumber)); // Code basically copied from SecKeychainSearchCreateFromAttributes and SecKeychainSearchCopyNext: KCCursor cursor(keychains, kSecCertificateItemClass, NULL); cursor->conjunctive(CSSM_DB_AND); cursor->add(CSSM_DB_EQUAL, Schema::kX509CertificateIssuer, issuerCSSM); cursor->add(CSSM_DB_EQUAL, Schema::kX509CertificateSerialNumber, serialNumberCSSM); return cursor; } KCCursor Certificate::cursorForSubjectKeyID(const StorageManager::KeychainList &keychains, const CssmData &subjectKeyID) { KCCursor cursor(keychains, kSecCertificateItemClass, NULL); cursor->conjunctive(CSSM_DB_AND); cursor->add(CSSM_DB_EQUAL, Schema::kX509CertificateSubjectKeyIdentifier, subjectKeyID); return cursor; } KCCursor Certificate::cursorForEmail(const StorageManager::KeychainList &keychains, const char *emailAddress) { KCCursor cursor(keychains, kSecCertificateItemClass, NULL); if (emailAddress) { cursor->conjunctive(CSSM_DB_AND); CssmSelectionPredicate &pred = cursor->add(CSSM_DB_EQUAL, Schema::kX509CertificateAlias, emailAddress); /* Normalize the emailAddresses in place since cursor already copied it. */ normalizeEmailAddress(pred.Attribute.Value[0]); } return cursor; } SecPointer Certificate::findInKeychain(const StorageManager::KeychainList &keychains) { StLock_(mMutex); const CSSM_OID &issuerOid = CSSMOID_X509V1IssuerName; CSSM_DATA_PTR issuerPtr = copyFirstFieldValue(issuerOid); CssmData issuer(issuerPtr->Data, issuerPtr->Length); const CSSM_OID &serialOid = CSSMOID_X509V1SerialNumber; CSSM_DATA_PTR serialPtr = copyFirstFieldValue(serialOid); CssmData serial(serialPtr->Data, serialPtr->Length); SecPointer foundCert = NULL; try { foundCert = findByIssuerAndSN(keychains, issuer, serial); } catch (...) { foundCert = NULL; } releaseFieldValue(issuerOid, issuerPtr); releaseFieldValue(serialOid, serialPtr); return foundCert; } SecPointer Certificate::findByIssuerAndSN(const StorageManager::KeychainList &keychains, const CssmData &issuer, const CssmData &serialNumber) { Item item; if (!cursorForIssuerAndSN(keychains, issuer, serialNumber)->next(item)) CssmError::throwMe(errSecItemNotFound); return static_cast(&*item); } SecPointer Certificate::findBySubjectKeyID(const StorageManager::KeychainList &keychains, const CssmData &subjectKeyID) { Item item; if (!cursorForSubjectKeyID(keychains, subjectKeyID)->next(item)) CssmError::throwMe(errSecItemNotFound); return static_cast(&*item); } SecPointer Certificate::findByEmail(const StorageManager::KeychainList &keychains, const char *emailAddress) { Item item; if (!cursorForEmail(keychains, emailAddress)->next(item)) CssmError::throwMe(errSecItemNotFound); return static_cast(&*item); } /* Normalize emailAddresses in place. */ void Certificate::normalizeEmailAddress(CSSM_DATA &emailAddress) { /* Do a check to see if a '\0' was at the end of emailAddress and strip it. */ if (emailAddress.Length && emailAddress.Data[emailAddress.Length - 1] == '\0') emailAddress.Length--; bool foundAt = false; for (uint32 ix = 0; ix < emailAddress.Length; ++ix) { uint8 ch = emailAddress.Data[ix]; if (foundAt) { if ('A' <= ch && ch <= 'Z') emailAddress.Data[ix] = ch + 'a' - 'A'; } else if (ch == '@') foundAt = true; } } void Certificate::getNames(CSSM_DATA_PTR *sanValues, CSSM_DATA_PTR snValue, CE_GeneralNameType generalNameType, std::vector &names) { // Get the DNS host names or RFC822 email addresses for this certificate (depending on generalNameType), // within the X509 SubjectAltName and SubjectName. // Find the SubjectAltName fields, if any, and extract the nameType entries from all of them if (sanValues) { for (CSSM_DATA_PTR *sanIx = sanValues; *sanIx; ++sanIx) { CSSM_DATA_PTR sanValue = *sanIx; if (sanValue && sanValue->Data) { CSSM_X509_EXTENSION *cssmExt = (CSSM_X509_EXTENSION *)sanValue->Data; CE_GeneralNames *parsedValue = (CE_GeneralNames *)cssmExt->value.parsedValue; /* Grab all the values that are of the specified name type. */ for (uint32 i = 0; i < parsedValue->numNames; ++i) { if (parsedValue->generalName[i].nameType == generalNameType) { if (parsedValue->generalName[i].berEncoded) // can't handle this continue; names.push_back(CssmData::overlay(parsedValue->generalName[i].name)); } } } } } if (names.empty() && snValue && snValue->Data) { const CSSM_X509_NAME &x509Name = *(const CSSM_X509_NAME *)snValue->Data; for (uint32 rdnDex = 0; rdnDex < x509Name.numberOfRDNs; rdnDex++) { const CSSM_X509_RDN *rdnPtr = &x509Name.RelativeDistinguishedName[rdnDex]; for (uint32 tvpDex = 0; tvpDex < rdnPtr->numberOfPairs; tvpDex++) { const CSSM_X509_TYPE_VALUE_PAIR *tvpPtr = &rdnPtr->AttributeTypeAndValue[tvpDex]; /* type/value pair: match caller's specified type */ if (GNT_RFC822Name == generalNameType) { if (((tvpPtr->type.Length != CSSMOID_EmailAddress.Length) || memcmp(tvpPtr->type.Data, CSSMOID_EmailAddress.Data, CSSMOID_EmailAddress.Length))) { continue; } } if (GNT_DNSName == generalNameType) { if (((tvpPtr->type.Length != CSSMOID_CommonName.Length) || memcmp(tvpPtr->type.Data, CSSMOID_CommonName.Data, CSSMOID_CommonName.Length))) { continue; } } /* printable? */ switch (tvpPtr->valueType) { case BER_TAG_PRINTABLE_STRING: case BER_TAG_IA5_STRING: case BER_TAG_T61_STRING: case BER_TAG_PKIX_UTF8_STRING: /* success */ names.push_back(CssmData::overlay(tvpPtr->value)); break; default: break; } } /* for each pair */ } /* for each RDN */ } } void Certificate::willRead() { populateAttributes(); } Boolean Certificate::isSelfSigned() { StLock_(mMutex); CSSM_DATA_PTR issuer = NULL; CSSM_DATA_PTR subject = NULL; OSStatus ortn = errSecSuccess; Boolean brtn = false; issuer = copyFirstFieldValue(CSSMOID_X509V1IssuerNameStd); subject = copyFirstFieldValue(CSSMOID_X509V1SubjectNameStd); if((issuer == NULL) || (subject == NULL)) { ortn = errSecParam; } else if((issuer->Length == subject->Length) && !memcmp(issuer->Data, subject->Data, issuer->Length)) { brtn = true; } if(brtn) { /* names match: verify signature */ CSSM_RETURN crtn; CSSM_DATA certData = data(); crtn = CSSM_CL_CertVerify(clHandle(), 0, &certData, &certData, NULL, 0); if(crtn) { brtn = false; } } if(issuer) { releaseFieldValue(CSSMOID_X509V1IssuerNameStd, issuer); } if(subject) { releaseFieldValue(CSSMOID_X509V1SubjectNameStd, subject); } if(ortn) { MacOSError::throwMe(ortn); } return brtn; }