/* * Copyright (C) 2013 Martin Willi * Copyright (C) 2013 revosec AG * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. See . * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ #include "keychain_creds.h" #include #include #include #include /** * System Roots keychain */ #define SYSTEM_ROOTS "/System/Library/Keychains/SystemRootCertificates.keychain" /** * System keychain */ #define SYSTEM "/Library/Keychains/System.keychain" typedef struct private_keychain_creds_t private_keychain_creds_t; /** * Private data of an keychain_creds_t object. */ struct private_keychain_creds_t { /** * Public keychain_creds_t interface. */ keychain_creds_t public; /** * Active in-memory credential set */ mem_cred_t *set; /** * System roots credential set */ mem_cred_t *roots; /** * Run loop of event monitoring thread */ CFRunLoopRef loop; }; /** * Load a credential sets with certificates from a keychain path */ static mem_cred_t* load_certs(private_keychain_creds_t *this, char *path) { SecKeychainRef keychain; SecKeychainSearchRef search; SecKeychainItemRef item; mem_cred_t *set; OSStatus status; int loaded = 0; set = mem_cred_create(); DBG2(DBG_CFG, "loading certificates from %s:", path); status = SecKeychainOpen(path, &keychain); if (status == errSecSuccess) { status = SecKeychainSearchCreateFromAttributes(keychain, kSecCertificateItemClass, NULL, &search); if (status == errSecSuccess) { while (SecKeychainSearchCopyNext(search, &item) == errSecSuccess) { certificate_t *cert; UInt32 len; void *data; if (SecKeychainItemCopyAttributesAndData(item, NULL, NULL, NULL, &len, &data) == errSecSuccess) { cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, BUILD_BLOB_ASN1_DER, chunk_create(data, len), BUILD_END); if (cert) { DBG2(DBG_CFG, " loaded '%Y'", cert->get_subject(cert)); set->add_cert(set, TRUE, cert); loaded++; } SecKeychainItemFreeAttributesAndData(NULL, data); } CFRelease(item); } CFRelease(search); } CFRelease(keychain); } DBG1(DBG_CFG, "loaded %d certificates from %s", loaded, path); return set; } /** * Callback function reloading keychain on changes */ static OSStatus keychain_cb(SecKeychainEvent keychainEvent, SecKeychainCallbackInfo *info, private_keychain_creds_t *this) { mem_cred_t *new; DBG1(DBG_CFG, "received keychain event, reloading credentials"); /* register new before removing old */ new = load_certs(this, SYSTEM); lib->credmgr->add_set(lib->credmgr, &new->set); lib->credmgr->remove_set(lib->credmgr, &this->set->set); lib->credmgr->flush_cache(lib->credmgr, CERT_X509); this->set->destroy(this->set); this->set = new; return errSecSuccess; } /** * Wait for changes in the keychain and handle them */ static job_requeue_t monitor_changes(private_keychain_creds_t *this) { if (SecKeychainAddCallback((SecKeychainCallback)keychain_cb, kSecAddEventMask | kSecDeleteEventMask | kSecUpdateEventMask | kSecTrustSettingsChangedEventMask, this) == errSecSuccess) { this->loop = CFRunLoopGetCurrent(); /* does not return until canceled */ CFRunLoopRun(); this->loop = NULL; SecKeychainRemoveCallback((SecKeychainCallback)keychain_cb); } return JOB_REQUEUE_NONE; } /** * Cancel the monitoring thread in its RunLoop */ static bool cancel_monitor(private_keychain_creds_t *this) { if (this->loop) { CFRunLoopStop(this->loop); } return TRUE; } METHOD(keychain_creds_t, destroy, void, private_keychain_creds_t *this) { lib->credmgr->remove_set(lib->credmgr, &this->set->set); lib->credmgr->remove_set(lib->credmgr, &this->roots->set); this->set->destroy(this->set); this->roots->destroy(this->roots); free(this); } /** * See header */ keychain_creds_t *keychain_creds_create() { private_keychain_creds_t *this; INIT(this, .public = { .destroy = _destroy, }, ); this->roots = load_certs(this, SYSTEM_ROOTS); this->set = load_certs(this, SYSTEM); lib->credmgr->add_set(lib->credmgr, &this->roots->set); lib->credmgr->add_set(lib->credmgr, &this->set->set); lib->processor->queue_job(lib->processor, (job_t*)callback_job_create_with_prio((void*)monitor_changes, this, NULL, (void*)cancel_monitor, JOB_PRIO_CRITICAL)); return &this->public; }