X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/Security/libsecurity_keychain/lib/Certificate.cpp?ds=inline diff --git a/Security/libsecurity_keychain/lib/Certificate.cpp b/Security/libsecurity_keychain/lib/Certificate.cpp new file mode 100644 index 00000000..76d6c1ce --- /dev/null +++ b/Security/libsecurity_keychain/lib/Certificate.cpp @@ -0,0 +1,1363 @@ +/* + * Copyright (c) 2002-2007,2011-2014 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; +}