+static Mutex& gParentCertCacheLock() {
+ static Mutex fParentCertCacheLock;
+ return fParentCertCacheLock;
+}
+static CFMutableDictionaryRef gParentCertCache;
+static CFMutableArrayRef gParentCertCacheList;
+#define PARENT_CACHE_SIZE 100
+
+void SecItemParentCachePurge() {
+ StLock<Mutex> _(gParentCertCacheLock());
+ CFReleaseNull(gParentCertCache);
+ CFReleaseNull(gParentCertCacheList);
+}
+
+static CFArrayRef CF_RETURNS_RETAINED parentCacheRead(SecCertificateRef certificate) {
+ CFArrayRef parents = NULL;
+ CFIndex ix;
+ CFDataRef digest = SecCertificateGetSHA1Digest(certificate);
+ if (!digest) return NULL;
+
+ StLock<Mutex> _(gParentCertCacheLock());
+ if (gParentCertCache && gParentCertCacheList) {
+ if (0 <= (ix = CFArrayGetFirstIndexOfValue(gParentCertCacheList,
+ CFRangeMake(0, CFArrayGetCount(gParentCertCacheList)),
+ digest))) {
+ // Cache hit. Get value and move entry to the top of the list.
+ parents = (CFArrayRef)CFDictionaryGetValue(gParentCertCache, digest);
+ CFArrayRemoveValueAtIndex(gParentCertCacheList, ix);
+ CFArrayAppendValue(gParentCertCacheList, digest);
+ }
+ }
+ CFRetainSafe(parents);
+ return parents;
+}
+
+static void parentCacheWrite(SecCertificateRef certificate, CFArrayRef parents) {
+ CFDataRef digest = SecCertificateGetSHA1Digest(certificate);
+ if (!digest) return;
+
+ StLock<Mutex> _(gParentCertCacheLock());
+ if (!gParentCertCache || !gParentCertCacheList) {
+ CFReleaseNull(gParentCertCache);
+ gParentCertCache = makeCFMutableDictionary();
+ CFReleaseNull(gParentCertCacheList);
+ gParentCertCacheList = makeCFMutableArray(0);
+ }
+
+ if (gParentCertCache && gParentCertCacheList) {
+ // check to make sure another thread didn't add this entry to the cache already
+ if (0 > CFArrayGetFirstIndexOfValue(gParentCertCacheList,
+ CFRangeMake(0, CFArrayGetCount(gParentCertCacheList)),
+ digest)) {
+ CFDictionaryAddValue(gParentCertCache, digest, parents);
+ if (PARENT_CACHE_SIZE <= CFArrayGetCount(gParentCertCacheList)) {
+ // Remove least recently used cache entry.
+ CFDictionaryRemoveValue(gParentCertCache, CFArrayGetValueAtIndex(gParentCertCacheList, 0));
+ CFArrayRemoveValueAtIndex(gParentCertCacheList, 0);
+ }
+ CFArrayAppendValue(gParentCertCacheList, digest);
+ }
+ }
+}
+
+/*
+ * SecItemCopyParentCertificates_osx returns an array of zero of more possible
+ * issuer certificates for the provided certificate. No cryptographic validation
+ * of the signature is performed in this function; its purpose is only to
+ * provide a list of candidate certificates.
+ */
+CFArrayRef
+SecItemCopyParentCertificates_osx(SecCertificateRef certificate, void *context)
+{
+#pragma unused (context) /* for now; in future this can reference a container object */
+ /* Check for parents in keychain cache */
+ CFArrayRef parents = parentCacheRead(certificate);
+ if (parents) {
+ return parents;
+ }
+
+ /* Cache miss. Query for parents. */
+#if TARGET_OS_OSX
+ CFDataRef normalizedIssuer = SecCertificateCopyNormalizedIssuerContent(certificate, NULL);
+#else
+ CFDataRef normalizedIssuer = SecCertificateGetNormalizedIssuerContent(certificate);
+ CFRetainSafe(normalizedIssuer);
+#endif
+ OSStatus status;
+ CFMutableArrayRef combinedSearchList = NULL;
+
+ /* Define the array of keychains which will be searched for parents. */
+ CFArrayRef searchList = NULL;
+ status = SecKeychainCopySearchList(&searchList);
+ if (searchList) {
+ combinedSearchList = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, searchList);
+ CFRelease(searchList);
+ } else {
+ combinedSearchList = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
+ }
+ SecKeychainRef rootStoreKeychain = NULL;
+ status = SecKeychainOpen(SYSTEM_ROOT_STORE_PATH, &rootStoreKeychain);
+ if (rootStoreKeychain) {
+ if (combinedSearchList) {
+ CFArrayAppendValue(combinedSearchList, rootStoreKeychain);
+ }
+ CFRelease(rootStoreKeychain);
+ }
+
+ /* Create and populate a fixed-size query dictionary. */
+ CFMutableDictionaryRef query = CFDictionaryCreateMutable(NULL, 5,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ CFDictionaryAddValue(query, kSecClass, kSecClassCertificate);
+ CFDictionaryAddValue(query, kSecReturnData, kCFBooleanTrue);
+ CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
+ if (combinedSearchList) {
+ CFDictionaryAddValue(query, kSecMatchSearchList, combinedSearchList);
+ CFRelease(combinedSearchList);
+ }
+
+ CFTypeRef results = NULL;
+ if (normalizedIssuer) {
+ /* Look up certs whose subject is the same as this cert's issuer. */
+ CFDictionaryAddValue(query, kSecAttrSubject, normalizedIssuer);
+ status = SecItemCopyMatching_osx(query, &results);
+ }
+ else {
+ /* Cannot match anything without an issuer! */
+ status = errSecItemNotFound;
+ }
+
+ if ((status != errSecSuccess) && (status != errSecItemNotFound)) {
+ secitemlog(LOG_WARNING, "SecItemCopyParentCertificates_osx: %d", (int)status);
+ }
+ CFRelease(query);
+
+ CFMutableArrayRef result = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
+ CFTypeID resultType = (results) ? CFGetTypeID(results) : 0;
+ if (resultType == CFArrayGetTypeID()) {
+ CFIndex index, count = CFArrayGetCount((CFArrayRef)results);
+ for (index = 0; index < count; index++) {
+ CFDataRef data = (CFDataRef) CFArrayGetValueAtIndex((CFArrayRef)results, index);
+ if (data) {
+ SecCertificateRef cert = SecCertificateCreateWithData(kCFAllocatorDefault, data);
+ if (cert) {
+ CFArrayAppendValue(result, cert);
+ CFRelease(cert);
+ }
+ }
+ }
+ } else if (results && resultType == CFDataGetTypeID()) {
+ SecCertificateRef cert = SecCertificateCreateWithData(kCFAllocatorDefault, (CFDataRef)results);
+ if (cert) {
+ CFArrayAppendValue(result, cert);
+ CFRelease(cert);
+ }
+ }
+ CFReleaseSafe(results);
+ CFReleaseSafe(normalizedIssuer);
+
+ /* Add to cache. */
+ parentCacheWrite(certificate, result);
+
+ return result;
+}
+
+SecCertificateRef SecItemCopyStoredCertificate(SecCertificateRef certificate, void *context)
+{
+#pragma unused (context) /* for now; in future this can reference a container object */
+
+ /* Certificates are unique by issuer and serial number. */
+ CFDataRef serialNumber = SecCertificateCopySerialNumberData(certificate, NULL);
+#if TARGET_OS_OSX
+ CFDataRef normalizedIssuer = SecCertificateCopyNormalizedIssuerContent(certificate, NULL);
+#else
+ CFDataRef normalizedIssuer = SecCertificateGetNormalizedIssuerContent(certificate);
+ CFRetainSafe(normalizedIssuer);
+#endif
+
+ const void *keys[] = {
+ kSecClass,
+ kSecMatchLimit,
+ kSecAttrIssuer,
+ kSecAttrSerialNumber,
+ kSecReturnRef
+ },
+ *values[] = {
+ kSecClassCertificate,
+ kSecMatchLimitOne,
+ normalizedIssuer,
+ serialNumber,
+ kCFBooleanTrue
+ };
+ CFDictionaryRef query = CFDictionaryCreate(NULL, keys, values, 5, NULL, NULL);
+ CFTypeRef result = NULL;
+
+ OSStatus status = SecItemCopyMatching_osx(query, &result);
+ if ((status != errSecSuccess) && (status != errSecItemNotFound)) {
+ secitemlog(LOG_WARNING, "SecItemCopyStoredCertificate: %d", (int)status);
+ CFReleaseNull(result);
+ }
+ CFReleaseSafe(query);
+ CFReleaseSafe(serialNumber);
+ CFReleaseSafe(normalizedIssuer);
+
+ return (SecCertificateRef)result;
+}
+