diff options
25 files changed, 2358 insertions, 40 deletions
diff --git a/configure.in b/configure.in index 53d06a3b4..f1524c24b 100644 --- a/configure.in +++ b/configure.in @@ -218,6 +218,7 @@ ARG_ENABL_SET([padlock], [enables VIA Padlock crypto plugin.]) ARG_ENABL_SET([openssl], [enables the OpenSSL crypto plugin.]) ARG_ENABL_SET([gcrypt], [enables the libgcrypt plugin.]) ARG_ENABL_SET([agent], [enables the ssh-agent signing plugin.]) +ARG_ENABL_SET([keychain], [enables OS X Keychain Services credential set.]) ARG_ENABL_SET([pkcs11], [enables the PKCS11 token support plugin.]) ARG_ENABL_SET([ctr], [enables the Counter Mode wrapper crypto plugin.]) ARG_ENABL_SET([ccm], [enables the CCM AEAD wrapper crypto plugin.]) @@ -1012,6 +1013,7 @@ ADD_PLUGIN([af-alg], [s charon openac scepclient pki scripts medsr ADD_PLUGIN([fips-prf], [s charon nm cmd]) ADD_PLUGIN([gmp], [s charon openac scepclient pki scripts manager medsrv attest nm cmd]) ADD_PLUGIN([agent], [s charon nm cmd]) +ADD_PLUGIN([keychain], [s charon cmd]) ADD_PLUGIN([xcbc], [s charon nm cmd]) ADD_PLUGIN([cmac], [s charon nm cmd]) ADD_PLUGIN([hmac], [s charon scripts nm cmd]) @@ -1148,6 +1150,7 @@ AM_CONDITIONAL(USE_PADLOCK, test x$padlock = xtrue) AM_CONDITIONAL(USE_OPENSSL, test x$openssl = xtrue) AM_CONDITIONAL(USE_GCRYPT, test x$gcrypt = xtrue) AM_CONDITIONAL(USE_AGENT, test x$agent = xtrue) +AM_CONDITIONAL(USE_KEYCHAIN, test x$keychain = xtrue) AM_CONDITIONAL(USE_PKCS11, test x$pkcs11 = xtrue) AM_CONDITIONAL(USE_CTR, test x$ctr = xtrue) AM_CONDITIONAL(USE_CCM, test x$ccm = xtrue) @@ -1349,6 +1352,7 @@ AC_CONFIG_FILES([ src/libstrongswan/plugins/openssl/Makefile src/libstrongswan/plugins/gcrypt/Makefile src/libstrongswan/plugins/agent/Makefile + src/libstrongswan/plugins/keychain/Makefile src/libstrongswan/plugins/pkcs11/Makefile src/libstrongswan/plugins/ctr/Makefile src/libstrongswan/plugins/ccm/Makefile diff --git a/src/frontends/osx/.gitignore b/src/frontends/osx/.gitignore new file mode 100644 index 000000000..f4be87183 --- /dev/null +++ b/src/frontends/osx/.gitignore @@ -0,0 +1,2 @@ +xcuserdata +*.xcworkspace diff --git a/src/frontends/osx/README.md b/src/frontends/osx/README.md new file mode 100644 index 000000000..bd24cce1b --- /dev/null +++ b/src/frontends/osx/README.md @@ -0,0 +1,98 @@ +# strongSwan OS X App # + +## Introduction ## + +The strongSwan OS X App consists of two components: + +* A frontend to configure and control connections +* A privileged helper daemon, controlled using XPC, called charon-xpc + +The privileged helper daemon gets installed automatically using SMJobBless +functionality on its first use, and gets started automatically by Launchd when +needed. + +charon-xpc is a special build linking statically against strongSwan components. + +## Building strongSwan ## + +strongSwan on OS X requires the libvstr library. The simplest way to install +it is using MacPorts. It gets statically linked to charon-xpc, hence it is not +needed to run the built App. + +Before building the Xcode project, the strongSwan base tree must be built using +a monolithic and static build. This can be achieved on OS X by using: + + LDFLAGS="-all_load -L/opt/local/lib" \ + CFLAGS="-idirafter /opt/local/include -O2 -Wall -Wno-format -Wno-pointer-sign" \ + ./configure --enable-monolithic --disable-shared --enable-static \ + --disable-defaults \ + --enable-openssl --enable-kernel-pfkey --enable-kernel-pfroute \ + --enable-eap-mschapv2 --enable-eap-identity --enable-nonce \ + --enable-random --enable-pkcs1 --enable-pem --enable-socket-default \ + --enable-xauth-generic --enable-keychain --enable-charon \ + --enable-ikev1 --enable-ikev2 + +followed by calling make (no need to make install). + +Building charon-xpc using the Xcode project yields a single binary without +any non OS X dependencies. + +Both charon-xpc and the App must be code-signed to allow the installation of +the privileged helper. git-grep for "Joe Developer" to change the signing +identity. + +## XPC application protocol ## + +charon-xpc provides a Mach service under the name _org.strongswan.charon-xpc_. +Clients can connect to this service to control the daemon. All messages +on all connections use the following string dictionary keys/values: + +* _type_: XPC message type, currently either + * _rpc_ for a remote procedure call, expects a response + * _event_ for application specific event messages +* _rpc_: defines the name of the RPC function to call (for _type_ = _rpc_) +* _event_: defines a name for the event (for _type_ = _event_) + +Additional arguments and return values are specified by the call and can have +any type. Keys are directly attached to the message dictionary. + +On the Mach service connection, the following RPC messages are currently +defined: + +* string version = get_version() + * _version_: strongSwan version of charon-xpc +* bool success = start_connection(string name, string host, string id, + endpoint channel) + * _success_: TRUE if initiation started successfully + * _name_: connection name to initiate + * _host_: server hostname (and identity) + * _id_: client identity to use + * _channel_: XPC endpoint for this connection + +The start_connection() RPC returns just after the initation of the call and +does not wait for the connection to establish. Nonetheless does it have a +return value to indicate if connection initiation could be triggered. + +The App passes an (anonymous) XPC endpoint to start_connection(). If the call +succeeds, charon-xpc connects to this endpoint to establish a channel used for +this specific IKE connection. + +On this channel, the following RPC calls are currently defined from charon-xpc +to the App: + +* string password = get_password(string username) + * _password_: user password returned + * _username_: username to query a password for + +And the following from the App to charon-xpc: + +* bool success = stop_connection() + * _success_: TRUE if termination of connection initiated + +The following events are currently defined from charon-xpc to the App: + +* up(): IKE_SA has been established +* down(): IKE_SA has been closed or failed to establish +* child_up(string local_ts, string remote_ts): CHILD_SA has been established +* child_down(string local_ts, string remote_ts): CHILD_SA has been closed +* log(string message): debug log message for this connection diff --git a/src/frontends/osx/charon-xpc/charon-xpc-Info.plist b/src/frontends/osx/charon-xpc/charon-xpc-Info.plist new file mode 100644 index 000000000..e8ddd24b0 --- /dev/null +++ b/src/frontends/osx/charon-xpc/charon-xpc-Info.plist @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleIdentifier</key> + <string>org.strongswan.charon-xpc</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>charon-xpc</string> + <key>CFBundleVersion</key> + <string>1.0</string> + <key>SMAuthorizedClients</key> + <array> + <string>identifier org.strongswan.osx and certificate leaf[subject.CN] = "Joe Developer"</string> + </array> +</dict> +</plist> diff --git a/src/frontends/osx/charon-xpc/charon-xpc-Launchd.plist b/src/frontends/osx/charon-xpc/charon-xpc-Launchd.plist new file mode 100644 index 000000000..703fab912 --- /dev/null +++ b/src/frontends/osx/charon-xpc/charon-xpc-Launchd.plist @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>Label</key> + <string>org.strongswan.charon-xpc</string> + <key>MachServices</key> + <dict> + <key>org.strongswan.charon-xpc</key> + <true/> + </dict> +</dict> +</plist> diff --git a/src/frontends/osx/charon-xpc/charon-xpc.c b/src/frontends/osx/charon-xpc/charon-xpc.c new file mode 100644 index 000000000..a1f64112a --- /dev/null +++ b/src/frontends/osx/charon-xpc/charon-xpc.c @@ -0,0 +1,207 @@ +/* + * 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 <http://www.fsf.org/copyleft/gpl.txt>. + * + * 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 <sys/types.h> +#include <sys/utsname.h> +#include <unistd.h> +#include <stdio.h> +#include <signal.h> +#include <pthread.h> + +#include <library.h> +#include <hydra.h> +#include <daemon.h> +#include <threading/thread.h> +#include <utils/backtrace.h> + +#include "xpc_dispatch.h" + +/** + * XPC dispatcher class + */ +static xpc_dispatch_t *dispatcher; + +/** + * atexit() cleanup for dispatcher + */ +void dispatcher_cleanup() +{ + DESTROY_IF(dispatcher); +} + +/** + * Loglevel configuration + */ +static level_t levels[DBG_MAX]; + +/** + * hook in library for debugging messages + */ +extern void (*dbg) (debug_t group, level_t level, char *fmt, ...); + +/** + * Logging hook for library logs, using stderr output + */ +static void dbg_stderr(debug_t group, level_t level, char *fmt, ...) +{ + va_list args; + + if (level <= 1) + { + va_start(args, fmt); + fprintf(stderr, "00[%N] ", debug_names, group); + vfprintf(stderr, fmt, args); + fprintf(stderr, "\n"); + va_end(args); + } +} + +/** + * Run the daemon and handle unix signals + */ +static int run() +{ + sigset_t set; + + sigemptyset(&set); + sigaddset(&set, SIGINT); + sigaddset(&set, SIGTERM); + sigprocmask(SIG_BLOCK, &set, NULL); + + while (TRUE) + { + int sig; + + if (sigwait(&set, &sig)) + { + DBG1(DBG_DMN, "error while waiting for a signal"); + return 1; + } + switch (sig) + { + case SIGINT: + DBG1(DBG_DMN, "signal of type SIGINT received. Shutting down"); + charon->bus->alert(charon->bus, ALERT_SHUTDOWN_SIGNAL, sig); + return 0; + case SIGTERM: + DBG1(DBG_DMN, "signal of type SIGTERM received. Shutting down"); + charon->bus->alert(charon->bus, ALERT_SHUTDOWN_SIGNAL, sig); + return 0; + default: + DBG1(DBG_DMN, "unknown signal %d received. Ignored", sig); + break; + } + } +} + +/** + * Handle SIGSEGV/SIGILL signals raised by threads + */ +static void segv_handler(int signal) +{ + backtrace_t *backtrace; + + DBG1(DBG_DMN, "thread %u received %d", thread_current_id(), signal); + backtrace = backtrace_create(2); + backtrace->log(backtrace, NULL, TRUE); + backtrace->destroy(backtrace); + + DBG1(DBG_DMN, "killing ourself, received critical signal"); + abort(); +} + +/** + * Main function, starts the daemon. + */ +int main(int argc, char *argv[]) +{ + struct sigaction action; + struct utsname utsname; + int group; + + dbg = dbg_stderr; + atexit(library_deinit); + if (!library_init(NULL)) + { + exit(SS_RC_LIBSTRONGSWAN_INTEGRITY); + } + if (lib->integrity) + { + if (!lib->integrity->check_file(lib->integrity, "charon-xpc", argv[0])) + { + exit(SS_RC_DAEMON_INTEGRITY); + } + } + atexit(libhydra_deinit); + if (!libhydra_init("charon-xpc")) + { + exit(SS_RC_INITIALIZATION_FAILED); + } + atexit(libcharon_deinit); + if (!libcharon_init("charon-xpc")) + { + exit(SS_RC_INITIALIZATION_FAILED); + } + for (group = 0; group < DBG_MAX; group++) + { + levels[group] = LEVEL_CTRL; + } + charon->load_loggers(charon, levels, TRUE); + + lib->settings->set_default_str(lib->settings, "charon-cmd.port", "0"); + lib->settings->set_default_str(lib->settings, "charon-cmd.port_nat_t", "0"); + lib->settings->set_default_str(lib->settings, + "charon-cmd.close_ike_on_child_failure", "yes"); + if (!charon->initialize(charon, + lib->settings->get_str(lib->settings, "charon-xpc.load", + "random nonce pem pkcs1 openssl kernel-pfkey kernel-pfroute " + "keychain socket-default eap-identity eap-mschapv2 osx-attr"))) + { + exit(SS_RC_INITIALIZATION_FAILED); + } + + if (uname(&utsname) != 0) + { + memset(&utsname, 0, sizeof(utsname)); + } + DBG1(DBG_DMN, "Starting charon-xpc IKE daemon (strongSwan %s, %s %s, %s)", + VERSION, utsname.sysname, utsname.release, utsname.machine); + + /* add handler for SEGV and ILL, + * INT, TERM and HUP are handled by sigwait() in run() */ + action.sa_handler = segv_handler; + action.sa_flags = 0; + sigemptyset(&action.sa_mask); + sigaddset(&action.sa_mask, SIGINT); + sigaddset(&action.sa_mask, SIGTERM); + sigaddset(&action.sa_mask, SIGHUP); + sigaction(SIGSEGV, &action, NULL); + sigaction(SIGILL, &action, NULL); + sigaction(SIGBUS, &action, NULL); + action.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &action, NULL); + + pthread_sigmask(SIG_SETMASK, &action.sa_mask, NULL); + + dispatcher = xpc_dispatch_create(); + if (!dispatcher) + { + exit(SS_RC_INITIALIZATION_FAILED); + } + atexit(dispatcher_cleanup); + + charon->start(charon); + return run(); +} diff --git a/src/frontends/osx/charon-xpc/xpc_channels.c b/src/frontends/osx/charon-xpc/xpc_channels.c new file mode 100644 index 000000000..9b5260047 --- /dev/null +++ b/src/frontends/osx/charon-xpc/xpc_channels.c @@ -0,0 +1,540 @@ +/* + * 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 <http://www.fsf.org/copyleft/gpl.txt>. + * + * 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 "xpc_channels.h" +#include "xpc_logger.h" + +#include <credentials/sets/callback_cred.h> +#include <collections/hashtable.h> +#include <threading/rwlock.h> +#include <daemon.h> + +typedef struct private_xpc_channels_t private_xpc_channels_t; + +/** + * Private data of an xpc_channels_t object. + */ +struct private_xpc_channels_t { + + /** + * Public xpc_channels_t interface. + */ + xpc_channels_t public; + + /** + * Registered channels, IKE_SA unique ID => entry_t + */ + hashtable_t *channels; + + /** + * Lock for channels list + */ + rwlock_t *lock; + + /** + * Callback credential set for passwords + */ + callback_cred_t *creds; +}; + +/** + * Channel entry + */ +typedef struct { + /* XPC channel to App */ + xpc_connection_t conn; + /* associated IKE_SA unique identifier */ + uintptr_t sa; + /* did we already ask for a password? */ + bool passworded; + /* channel specific logger */ + xpc_logger_t *logger; +} entry_t; + +/** + * Clean up an entry, cancelling connection + */ +static void destroy_entry(entry_t *entry) +{ + charon->bus->remove_logger(charon->bus, &entry->logger->logger); + entry->logger->destroy(entry->logger); + xpc_connection_suspend(entry->conn); + xpc_release(entry->conn); + free(entry); +} + +/** + * Find an IKE_SA unique identifier by a given XPC channel + */ +static u_int32_t find_ike_sa_by_conn(private_xpc_channels_t *this, + xpc_connection_t conn) +{ + enumerator_t *enumerator; + entry_t *entry; + u_int32_t ike_sa = 0; + + this->lock->read_lock(this->lock); + enumerator = this->channels->create_enumerator(this->channels); + while (enumerator->enumerate(enumerator, NULL, &entry)) + { + if (entry->conn == conn) + { + ike_sa = entry->sa; + break; + } + } + enumerator->destroy(enumerator); + this->lock->unlock(this->lock); + + return ike_sa; +} + +/** + * Remove an entry for a given XPC connection + */ +static void remove_conn(private_xpc_channels_t *this, xpc_connection_t conn) +{ + uintptr_t ike_sa; + entry_t *entry; + + ike_sa = find_ike_sa_by_conn(this, conn); + if (ike_sa) + { + this->lock->write_lock(this->lock); + entry = this->channels->remove(this->channels, (void*)ike_sa); + this->lock->unlock(this->lock); + + if (entry) + { + destroy_entry(entry); + } + } +} + +/** + * Trigger termination of a connection + */ +static void stop_connection(private_xpc_channels_t *this, u_int32_t ike_sa, + xpc_object_t request, xpc_object_t reply) +{ + status_t status; + + status = charon->controller->terminate_ike(charon->controller, ike_sa, + NULL, NULL, 0); + xpc_dictionary_set_bool(reply, "success", status != NOT_FOUND); +} + +/** + * XPC RPC command dispatch table + */ +static struct { + char *name; + void (*handler)(private_xpc_channels_t *this, u_int32_t ike_sa, + xpc_object_t request, xpc_object_t reply); +} commands[] = { + { "stop_connection", stop_connection }, +}; + +/** + * Handle a request message from App + */ +static void handle(private_xpc_channels_t *this, xpc_connection_t conn, + xpc_object_t request) +{ + xpc_object_t reply; + const char *type, *rpc; + bool found = FALSE; + u_int32_t ike_sa; + int i; + + type = xpc_dictionary_get_string(request, "type"); + if (type) + { + if (streq(type, "rpc")) + { + reply = xpc_dictionary_create_reply(request); + rpc = xpc_dictionary_get_string(request, "rpc"); + ike_sa = find_ike_sa_by_conn(this, conn); + if (reply && rpc && ike_sa) + { + for (i = 0; i < countof(commands); i++) + { + if (streq(commands[i].name, rpc)) + { + found = TRUE; + commands[i].handler(this, ike_sa, request, reply); + break; + } + } + } + if (!found) + { + DBG1(DBG_CFG, "received invalid XPC rpc command: %s", rpc); + } + if (reply) + { + xpc_connection_send_message(conn, reply); + xpc_release(reply); + } + } + else + { + DBG1(DBG_CFG, "received unknown XPC message type: %s", type); + } + } + else + { + DBG1(DBG_CFG, "received XPC message without a type"); + } +} + +METHOD(xpc_channels_t, add, void, + private_xpc_channels_t *this, xpc_connection_t conn, u_int32_t ike_sa) +{ + entry_t *entry; + + INIT(entry, + .conn = conn, + .sa = ike_sa, + .logger = xpc_logger_create(conn), + ); + + xpc_connection_set_event_handler(entry->conn, ^(xpc_object_t event) + { + if (event == XPC_ERROR_CONNECTION_INVALID || + event == XPC_ERROR_CONNECTION_INTERRUPTED) + { + remove_conn(this, conn); + } + else if (xpc_get_type(event) == XPC_TYPE_DICTIONARY) + { + handle(this, conn, event); + } + }); + + entry->logger->set_ike_sa(entry->logger, entry->sa); + charon->bus->add_logger(charon->bus, &entry->logger->logger); + + this->lock->write_lock(this->lock); + this->channels->put(this->channels, (void*)entry->sa, entry); + this->lock->unlock(this->lock); + + xpc_connection_resume(conn); +} + +METHOD(listener_t, alert, bool, + private_xpc_channels_t *this, ike_sa_t *ike_sa, alert_t alert, va_list args) +{ + const char *desc; + + switch (alert) + { + case ALERT_LOCAL_AUTH_FAILED: + desc = "local-auth"; + break; + case ALERT_PEER_AUTH_FAILED: + desc = "remote-auth"; + break; + case ALERT_PEER_ADDR_FAILED: + desc = "dns"; + break; + case ALERT_PEER_INIT_UNREACHABLE: + desc = "unreachable"; + break; + case ALERT_RETRANSMIT_SEND_TIMEOUT: + desc = "timeout"; + break; + case ALERT_PROPOSAL_MISMATCH_IKE: + case ALERT_PROPOSAL_MISMATCH_CHILD: + desc = "proposal-mismatch"; + break; + case ALERT_TS_MISMATCH: + desc = "ts-mismatch"; + break; + default: + return TRUE; + } + if (ike_sa) + { + entry_t *entry; + uintptr_t sa; + + sa = ike_sa->get_unique_id(ike_sa); + this->lock->read_lock(this->lock); + entry = this->channels->get(this->channels, (void*)sa); + if (entry) + { + xpc_object_t msg; + + msg = xpc_dictionary_create(NULL, NULL, 0); + xpc_dictionary_set_string(msg, "type", "event"); + xpc_dictionary_set_string(msg, "event", "alert"); + xpc_dictionary_set_string(msg, "alert", desc); + xpc_connection_send_message(entry->conn, msg); + xpc_release(msg); + } + this->lock->unlock(this->lock); + } + return TRUE; +} + +METHOD(listener_t, ike_rekey, bool, + private_xpc_channels_t *this, ike_sa_t *old, ike_sa_t *new) +{ + entry_t *entry; + uintptr_t sa; + + sa = old->get_unique_id(old); + this->lock->write_lock(this->lock); + entry = this->channels->remove(this->channels, (void*)sa); + if (entry) + { + entry->sa = new->get_unique_id(new); + entry->logger->set_ike_sa(entry->logger, entry->sa); + this->channels->put(this->channels, (void*)entry->sa, entry); + } + this->lock->unlock(this->lock); + + return TRUE; +} + +METHOD(listener_t, ike_state_change, bool, + private_xpc_channels_t *this, ike_sa_t *ike_sa, ike_sa_state_t state) +{ + if (state == IKE_CONNECTING) + { + entry_t *entry; + uintptr_t sa; + + sa = ike_sa->get_unique_id(ike_sa); + this->lock->read_lock(this->lock); + entry = this->channels->get(this->channels, (void*)sa); + if (entry) + { + xpc_object_t msg; + + msg = xpc_dictionary_create(NULL, NULL, 0); + xpc_dictionary_set_string(msg, "type", "event"); + xpc_dictionary_set_string(msg, "event", "connecting"); + xpc_connection_send_message(entry->conn, msg); + xpc_release(msg); + } + this->lock->unlock(this->lock); + } + return TRUE; +} + +METHOD(listener_t, child_updown, bool, + private_xpc_channels_t *this, ike_sa_t *ike_sa, + child_sa_t *child_sa, bool up) +{ + entry_t *entry; + uintptr_t sa; + + sa = ike_sa->get_unique_id(ike_sa); + this->lock->read_lock(this->lock); + entry = this->channels->get(this->channels, (void*)sa); + if (entry) + { + xpc_object_t msg; + linked_list_t *list; + char buf[256]; + + msg = xpc_dictionary_create(NULL, NULL, 0); + xpc_dictionary_set_string(msg, "type", "event"); + if (up) + { + xpc_dictionary_set_string(msg, "event", "child_up"); + } + else + { + xpc_dictionary_set_string(msg, "event", "child_down"); + } + + list = child_sa->get_traffic_selectors(child_sa, TRUE); + snprintf(buf, sizeof(buf), "%#R", list); + xpc_dictionary_set_string(msg, "ts_local", buf); + + list = child_sa->get_traffic_selectors(child_sa, FALSE); + snprintf(buf, sizeof(buf), "%#R", list); + xpc_dictionary_set_string(msg, "ts_remote", buf); + + xpc_connection_send_message(entry->conn, msg); + xpc_release(msg); + } + this->lock->unlock(this->lock); + return TRUE; +} + +METHOD(listener_t, ike_updown, bool, + private_xpc_channels_t *this, ike_sa_t *ike_sa, bool up) +{ + xpc_object_t msg; + entry_t *entry; + uintptr_t sa; + + sa = ike_sa->get_unique_id(ike_sa); + if (up) + { + this->lock->read_lock(this->lock); + entry = this->channels->get(this->channels, (void*)sa); + if (entry) + { + msg = xpc_dictionary_create(NULL, NULL, 0); + xpc_dictionary_set_string(msg, "type", "event"); + xpc_dictionary_set_string(msg, "event", "up"); + xpc_connection_send_message(entry->conn, msg); + xpc_release(msg); + } + this->lock->unlock(this->lock); + } + else + { + this->lock->write_lock(this->lock); + entry = this->channels->remove(this->channels, (void*)sa); + this->lock->unlock(this->lock); + if (entry) + { + msg = xpc_dictionary_create(NULL, NULL, 0); + xpc_dictionary_set_string(msg, "type", "event"); + xpc_dictionary_set_string(msg, "event", "down"); + xpc_connection_send_message(entry->conn, msg); + xpc_release(msg); + xpc_connection_send_barrier(entry->conn, ^() { + destroy_entry(entry); + }); + } + } + return TRUE; +} + +/** + * Query password from App using XPC channel + */ +static shared_key_t *query_password(xpc_connection_t conn, identification_t *id) +{ + char buf[128], *password; + xpc_object_t request, response; + shared_key_t *shared = NULL; + + request = xpc_dictionary_create(NULL, NULL, 0); + xpc_dictionary_set_string(request, "type", "rpc"); + xpc_dictionary_set_string(request, "rpc", "get_password"); + snprintf(buf, sizeof(buf), "%Y", id); + xpc_dictionary_set_string(request, "username", buf); + + response = xpc_connection_send_message_with_reply_sync(conn, request); + xpc_release(request); + if (xpc_get_type(response) == XPC_TYPE_DICTIONARY) + { + password = (char*)xpc_dictionary_get_string(response, "password"); + if (password) + { + shared = shared_key_create(SHARED_EAP, + chunk_clone(chunk_from_str(password))); + } + } + xpc_release(response); + return shared; +} + +/** + * Password query callback + */ +static shared_key_t* password_cb(private_xpc_channels_t *this, + shared_key_type_t type, + identification_t *me, identification_t *other, + id_match_t *match_me, id_match_t *match_other) +{ + shared_key_t *shared = NULL; + ike_sa_t *ike_sa; + entry_t *entry; + u_int32_t sa; + + switch (type) + { + case SHARED_EAP: + break; + default: + return NULL; + } + ike_sa = charon->bus->get_sa(charon->bus); + if (ike_sa) + { + sa = ike_sa->get_unique_id(ike_sa); + this->lock->read_lock(this->lock); + entry = this->channels->get(this->channels, (void*)sa); + if (entry && !entry->passworded) + { + entry->passworded = TRUE; + + shared = query_password(entry->conn, me); + if (shared) + { + if (match_me) + { + *match_me = ID_MATCH_PERFECT; + } + if (match_other) + { + *match_other = ID_MATCH_PERFECT; + } + } + } + this->lock->unlock(this->lock); + } + return shared; +} + +METHOD(xpc_channels_t, destroy, void, + private_xpc_channels_t *this) +{ + lib->credmgr->remove_set(lib->credmgr, &this->creds->set); + this->creds->destroy(this->creds); + this->channels->destroy(this->channels); + this->lock->destroy(this->lock); + free(this); +} + +/** + * See header + */ +xpc_channels_t *xpc_channels_create() +{ + private_xpc_channels_t *this; + + INIT(this, + .public = { + .listener = { + .alert = _alert, + .ike_updown = _ike_updown, + .ike_rekey = _ike_rekey, + .ike_state_change = _ike_state_change, + .child_updown = _child_updown, + }, + .add = _add, + .destroy = _destroy, + }, + .channels = hashtable_create(hashtable_hash_ptr, + hashtable_equals_ptr, 4), + .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), + ); + + this->creds = callback_cred_create_shared( + (callback_cred_shared_cb_t)password_cb, this); + lib->credmgr->add_set(lib->credmgr, &this->creds->set); + + return &this->public; +} diff --git a/src/frontends/osx/charon-xpc/xpc_channels.h b/src/frontends/osx/charon-xpc/xpc_channels.h new file mode 100644 index 000000000..125a81f1d --- /dev/null +++ b/src/frontends/osx/charon-xpc/xpc_channels.h @@ -0,0 +1,59 @@ +/* + * 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 <http://www.fsf.org/copyleft/gpl.txt>. + * + * 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. + */ + +/** + * @defgroup xpc_channels xpc_channels + * @{ @ingroup xpc + */ + +#ifndef XPC_CHANNELS_H_ +#define XPC_CHANNELS_H_ + +#include <xpc/xpc.h> + +#include <bus/bus.h> + +typedef struct xpc_channels_t xpc_channels_t; + +/** + * XPC to App channel management. + */ +struct xpc_channels_t { + + /** + * Implements listener_t. + */ + listener_t listener; + + /** + * Associate an IKE_SA unique identifier to an XPC connection. + * + * @param conn XPC connection to channel + * @param ike_sa IKE_SA unique identifier to associate to connection + */ + void (*add)(xpc_channels_t *this, xpc_connection_t conn, u_int32_t ike_sa); + + /** + * Destroy a xpc_channels_t. + */ + void (*destroy)(xpc_channels_t *this); +}; + +/** + * Create a xpc_channels instance. + */ +xpc_channels_t *xpc_channels_create(); + +#endif /** XPC_CHANNELS_H_ @}*/ diff --git a/src/frontends/osx/charon-xpc/xpc_dispatch.c b/src/frontends/osx/charon-xpc/xpc_dispatch.c new file mode 100644 index 000000000..f60bcbfe0 --- /dev/null +++ b/src/frontends/osx/charon-xpc/xpc_dispatch.c @@ -0,0 +1,341 @@ +/* + * 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 <http://www.fsf.org/copyleft/gpl.txt>. + * + * 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 "xpc_dispatch.h" +#include "xpc_channels.h" + +#include <xpc/xpc.h> +#include <signal.h> +#include <unistd.h> + +#include <daemon.h> +#include <processing/jobs/callback_job.h> + +typedef struct private_xpc_dispatch_t private_xpc_dispatch_t; + +/** + * Private data of an xpc_dispatch_t object. + */ +struct private_xpc_dispatch_t { + + /** + * Public xpc_dispatch_t interface. + */ + xpc_dispatch_t public; + + /** + * XPC service we offer + */ + xpc_connection_t service; + + /** + * XPC IKE_SA specific channels to App + */ + xpc_channels_t *channels; + + /** + * GCD queue for XPC events + */ + dispatch_queue_t queue; + + /** + * Number of active App connections + */ + refcount_t refcount; + + /** + * PID of main thread + */ + pid_t pid; +}; + +/** + * Return version of this helper + */ +static void get_version(private_xpc_dispatch_t *this, + xpc_object_t request, xpc_object_t reply) +{ + xpc_dictionary_set_string(reply, "version", PACKAGE_VERSION); +} + +/** + * Create peer config with associated ike config + */ +static peer_cfg_t* create_peer_cfg(char *name, char *host) +{ + ike_cfg_t *ike_cfg; + peer_cfg_t *peer_cfg; + u_int16_t local_port, remote_port = IKEV2_UDP_PORT; + + local_port = charon->socket->get_port(charon->socket, FALSE); + if (local_port != IKEV2_UDP_PORT) + { + remote_port = IKEV2_NATT_PORT; + } + ike_cfg = ike_cfg_create(IKEV2, FALSE, FALSE, "0.0.0.0", FALSE, local_port, + host, FALSE, remote_port, FRAGMENTATION_NO, 0); + ike_cfg->add_proposal(ike_cfg, proposal_create_default(PROTO_IKE)); + peer_cfg = peer_cfg_create(name, ike_cfg, + CERT_SEND_IF_ASKED, UNIQUE_REPLACE, 1, /* keyingtries */ + 36000, 0, /* rekey 10h, reauth none */ + 600, 600, /* jitter, over 10min */ + TRUE, FALSE, /* mobike, aggressive */ + 30, 0, /* DPD delay, timeout */ + FALSE, NULL, NULL); /* mediation */ + peer_cfg->add_virtual_ip(peer_cfg, host_create_from_string("0.0.0.0", 0)); + + return peer_cfg; +} + +/** + * Add a single auth cfg of given class to peer cfg + */ +static void add_auth_cfg(peer_cfg_t *peer_cfg, bool local, + char *id, auth_class_t class) +{ + auth_cfg_t *auth; + + auth = auth_cfg_create(); + auth->add(auth, AUTH_RULE_AUTH_CLASS, class); + auth->add(auth, AUTH_RULE_IDENTITY, identification_create_from_string(id)); + peer_cfg->add_auth_cfg(peer_cfg, auth, local); +} + +/** + * Attach child config to peer config + */ +static child_cfg_t* create_child_cfg(char *name) +{ + child_cfg_t *child_cfg; + traffic_selector_t *ts; + lifetime_cfg_t lifetime = { + .time = { + .life = 10800 /* 3h */, + .rekey = 10200 /* 2h50min */, + .jitter = 300 /* 5min */ + } + }; + + child_cfg = child_cfg_create(name, &lifetime, + NULL, FALSE, MODE_TUNNEL, /* updown, hostaccess */ + ACTION_NONE, ACTION_NONE, ACTION_NONE, FALSE, + 0, 0, NULL, NULL, 0); + child_cfg->add_proposal(child_cfg, proposal_create_default(PROTO_ESP)); + ts = traffic_selector_create_dynamic(0, 0, 65535); + child_cfg->add_traffic_selector(child_cfg, TRUE, ts); + ts = traffic_selector_create_from_string(0, TS_IPV4_ADDR_RANGE, + "0.0.0.0", 0, "255.255.255.255", 65535); + child_cfg->add_traffic_selector(child_cfg, FALSE, ts); + + return child_cfg; +} + +/** + * Controller initiate callback + */ +static bool initiate_cb(u_int32_t *sa, debug_t group, level_t level, + ike_sa_t *ike_sa, const char *message) +{ + if (ike_sa) + { + *sa = ike_sa->get_unique_id(ike_sa); + return FALSE; + } + return TRUE; +} + +/** + * Start initiating an IKE connection + */ +void start_connection(private_xpc_dispatch_t *this, + xpc_object_t request, xpc_object_t reply) +{ + peer_cfg_t *peer_cfg; + child_cfg_t *child_cfg; + char *name, *id, *host; + bool success = FALSE; + xpc_endpoint_t endpoint; + xpc_connection_t channel; + u_int32_t ike_sa; + + name = (char*)xpc_dictionary_get_string(request, "name"); + host = (char*)xpc_dictionary_get_string(request, "host"); + id = (char*)xpc_dictionary_get_string(request, "id"); + endpoint = xpc_dictionary_get_value(request, "channel"); + channel = xpc_connection_create_from_endpoint(endpoint); + + if (name && id && host && channel) + { + peer_cfg = create_peer_cfg(name, host); + + add_auth_cfg(peer_cfg, TRUE, id, AUTH_CLASS_EAP); + add_auth_cfg(peer_cfg, FALSE, host, AUTH_CLASS_ANY); + + child_cfg = create_child_cfg(name); + peer_cfg->add_child_cfg(peer_cfg, child_cfg->get_ref(child_cfg)); + + if (charon->controller->initiate(charon->controller, peer_cfg, child_cfg, + (controller_cb_t)initiate_cb, &ike_sa, 0) == NEED_MORE) + { + this->channels->add(this->channels, channel, ike_sa); + success = TRUE; + } + } + + xpc_dictionary_set_bool(reply, "success", success); +} + +/** + * XPC RPC command dispatch table + */ +static struct { + char *name; + void (*handler)(private_xpc_dispatch_t *this, + xpc_object_t request, xpc_object_t reply); +} commands[] = { + { "get_version", get_version }, + { "start_connection", start_connection }, +}; + +/** + * Handle a received XPC request message + */ +static void handle(private_xpc_dispatch_t *this, xpc_object_t request) +{ + xpc_connection_t client; + xpc_object_t reply; + const char *type, *rpc; + bool found = FALSE; + int i; + + type = xpc_dictionary_get_string(request, "type"); + if (type) + { + if (streq(type, "rpc")) + { + reply = xpc_dictionary_create_reply(request); + rpc = xpc_dictionary_get_string(request, "rpc"); + if (reply && rpc) + { + for (i = 0; i < countof(commands); i++) + { + if (streq(commands[i].name, rpc)) + { + found = TRUE; + commands[i].handler(this, request, reply); + break; + } + } + } + if (!found) + { + DBG1(DBG_CFG, "received invalid XPC rpc command: %s", rpc); + } + if (reply) + { + client = xpc_dictionary_get_remote_connection(request); + xpc_connection_send_message(client, reply); + xpc_release(reply); + } + } + else + { + DBG1(DBG_CFG, "received unknown XPC message type: %s", type); + } + } + else + { + DBG1(DBG_CFG, "received XPC message without a type"); + } +} + +/** + * Finalizer for client connections + */ +static void cleanup_connection(private_xpc_dispatch_t *this) +{ + if (ref_put(&this->refcount)) + { + DBG1(DBG_CFG, "no XPC connections, raising SIGTERM"); + kill(this->pid, SIGTERM); + } +} + +/** + * Set up GCD handler for XPC events + */ +static void set_handler(private_xpc_dispatch_t *this) +{ + xpc_connection_set_event_handler(this->service, ^(xpc_object_t conn) + { + xpc_connection_set_event_handler(conn, ^(xpc_object_t event) + { + if (xpc_get_type(event) == XPC_TYPE_DICTIONARY) + { + handle(this, event); + } + }); + ref_get(&this->refcount); + xpc_connection_set_context(conn, this); + xpc_connection_set_finalizer_f(conn, (void*)cleanup_connection); + xpc_connection_resume(conn); + }); + xpc_connection_resume(this->service); +} + +METHOD(xpc_dispatch_t, destroy, void, + private_xpc_dispatch_t *this) +{ + charon->bus->remove_listener(charon->bus, &this->channels->listener); + this->channels->destroy(this->channels); + if (this->service) + { + xpc_connection_suspend(this->service); + xpc_release(this->service); + } + free(this); +} + +/** + * See header + */ +xpc_dispatch_t *xpc_dispatch_create() +{ + private_xpc_dispatch_t *this; + + INIT(this, + .public = { + .destroy = _destroy, + }, + .channels = xpc_channels_create(), + .queue = dispatch_queue_create("org.strongswan.charon-xpc.q", + DISPATCH_QUEUE_CONCURRENT), + .pid = getpid(), + ); + charon->bus->add_listener(charon->bus, &this->channels->listener); + + this->service = xpc_connection_create_mach_service( + "org.strongswan.charon-xpc", this->queue, + XPC_CONNECTION_MACH_SERVICE_LISTENER); + if (!this->service) + { + destroy(this); + return NULL; + } + + set_handler(this); + + return &this->public; +} diff --git a/src/frontends/osx/charon-xpc/xpc_dispatch.h b/src/frontends/osx/charon-xpc/xpc_dispatch.h new file mode 100644 index 000000000..9f40e6027 --- /dev/null +++ b/src/frontends/osx/charon-xpc/xpc_dispatch.h @@ -0,0 +1,42 @@ +/* + * 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 <http://www.fsf.org/copyleft/gpl.txt>. + * + * 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. + */ + +/** + * @defgroup xpc_dispatch xpc_dispatch + * @{ @ingroup xpc + */ + +#ifndef XPC_DISPATCH_H_ +#define XPC_DISPATCH_H_ + +typedef struct xpc_dispatch_t xpc_dispatch_t; + +/** + * XPC dispatcher to control the daemon. + */ +struct xpc_dispatch_t { + + /** + * Destroy a xpc_dispatch_t. + */ + void (*destroy)(xpc_dispatch_t *this); +}; + +/** + * Create a xpc_dispatch instance. + */ +xpc_dispatch_t *xpc_dispatch_create(); + +#endif /** XPC_DISPATCH_H_ @}*/ diff --git a/src/frontends/osx/charon-xpc/xpc_logger.c b/src/frontends/osx/charon-xpc/xpc_logger.c new file mode 100644 index 000000000..38c34e460 --- /dev/null +++ b/src/frontends/osx/charon-xpc/xpc_logger.c @@ -0,0 +1,96 @@ +/* + * 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 <http://www.fsf.org/copyleft/gpl.txt>. + * + * 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 "xpc_logger.h" + +typedef struct private_xpc_logger_t private_xpc_logger_t; + +/** + * Private data of an xpc_logger_t object. + */ +struct private_xpc_logger_t { + + /** + * Public xpc_logger_t interface. + */ + xpc_logger_t public; + + /** + * XPC channel to send logging messages to + */ + xpc_connection_t conn; + + /** + * IKE_SA we log for + */ + u_int32_t ike_sa; +}; + +METHOD(logger_t, log_, void, + private_xpc_logger_t *this, debug_t group, level_t level, int thread, + ike_sa_t* ike_sa, const char *message) +{ + if (ike_sa && ike_sa->get_unique_id(ike_sa) == this->ike_sa) + { + xpc_object_t msg; + + msg = xpc_dictionary_create(NULL, NULL, 0); + xpc_dictionary_set_string(msg, "type", "event"); + xpc_dictionary_set_string(msg, "event", "log"); + xpc_dictionary_set_string(msg, "message", message); + xpc_connection_send_message(this->conn, msg); + xpc_release(msg); + } +} + +METHOD(logger_t, get_level, level_t, + private_xpc_logger_t *this, debug_t group) +{ + return LEVEL_CTRL; +} + +METHOD(xpc_logger_t, set_ike_sa, void, + private_xpc_logger_t *this, u_int32_t ike_sa) +{ + this->ike_sa = ike_sa; +} + +METHOD(xpc_logger_t, destroy, void, + private_xpc_logger_t *this) +{ + free(this); +} + +/** + * See header + */ +xpc_logger_t *xpc_logger_create(xpc_connection_t conn) +{ + private_xpc_logger_t *this; + + INIT(this, + .public = { + .logger = { + .log = _log_, + .get_level = _get_level, + }, + .set_ike_sa = _set_ike_sa, + .destroy = _destroy, + }, + .conn = conn, + ); + + return &this->public; +} diff --git a/src/frontends/osx/charon-xpc/xpc_logger.h b/src/frontends/osx/charon-xpc/xpc_logger.h new file mode 100644 index 000000000..fd5ad37a2 --- /dev/null +++ b/src/frontends/osx/charon-xpc/xpc_logger.h @@ -0,0 +1,61 @@ +/* + * 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 <http://www.fsf.org/copyleft/gpl.txt>. + * + * 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. + */ + +/** + * @defgroup xpc_logger xpc_logger + * @{ @ingroup xpc + */ + +#ifndef XPC_LOGGER_H_ +#define XPC_LOGGER_H_ + +#include <xpc/xpc.h> + +#include <daemon.h> + +typedef struct xpc_logger_t xpc_logger_t; + +/** + * Connection specific logger over XPC. + */ +struct xpc_logger_t { + + /** + * Implements logger_t. + */ + logger_t logger; + + /** + * Set the IKE_SA unique identifier this logger logs for. + * + * @param ike_sa IKE_SA unique identifier + */ + void (*set_ike_sa)(xpc_logger_t *this, u_int32_t ike_sa); + + /** + * Destroy a xpc_logger_t. + */ + void (*destroy)(xpc_logger_t *this); +}; + +/** + * Create a xpc_logger instance. + * + * @param conn XPC connection to send logging events to + * @return XPC logger + */ +xpc_logger_t *xpc_logger_create(xpc_connection_t conn); + +#endif /** XPC_LOGGER_H_ @}*/ diff --git a/src/frontends/osx/strongSwan.xcodeproj/project.pbxproj b/src/frontends/osx/strongSwan.xcodeproj/project.pbxproj new file mode 100644 index 000000000..2eb88b77a --- /dev/null +++ b/src/frontends/osx/strongSwan.xcodeproj/project.pbxproj @@ -0,0 +1,333 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 5B74989217311B200041971E /* xpc_channels.c in Sources */ = {isa = PBXBuildFile; fileRef = 5B74989117311B200041971E /* xpc_channels.c */; }; + 5B7498B8173275D10041971E /* xpc_logger.c in Sources */ = {isa = PBXBuildFile; fileRef = 5B7498B7173275D10041971E /* xpc_logger.c */; }; + 5BD1CCD71726DB4000587077 /* charon-xpc.c in Sources */ = {isa = PBXBuildFile; fileRef = 5BD1CCD61726DB4000587077 /* charon-xpc.c */; }; + 5BF60F31173405A000E5D608 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5BD1CCD31726DB4000587077 /* CoreFoundation.framework */; }; + 5BF60F33173405AC00E5D608 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5BD1CCF21727DE3E00587077 /* Security.framework */; }; + 5BF60F3E1734070A00E5D608 /* xpc_dispatch.c in Sources */ = {isa = PBXBuildFile; fileRef = 5B74984C172AA3550041971E /* xpc_dispatch.c */; }; + 5BF60F631743C57500E5D608 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5BF60F621743C57500E5D608 /* SystemConfiguration.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 5B74984C172AA3550041971E /* xpc_dispatch.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = xpc_dispatch.c; sourceTree = "<group>"; }; + 5B74984E172AA3670041971E /* xpc_dispatch.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = xpc_dispatch.h; sourceTree = "<group>"; }; + 5B74989017311AFC0041971E /* xpc_channels.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = xpc_channels.h; sourceTree = "<group>"; }; + 5B74989117311B200041971E /* xpc_channels.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = xpc_channels.c; sourceTree = "<group>"; }; + 5B7498B7173275D10041971E /* xpc_logger.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = xpc_logger.c; sourceTree = "<group>"; }; + 5B7498B9173275DD0041971E /* xpc_logger.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = xpc_logger.h; sourceTree = "<group>"; }; + 5BD1CCD31726DB4000587077 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; + 5BD1CCD61726DB4000587077 /* charon-xpc.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = "charon-xpc.c"; sourceTree = "<group>"; }; + 5BD1CCE01726DCD000587077 /* charon-xpc-Launchd.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "charon-xpc-Launchd.plist"; sourceTree = "<group>"; }; + 5BD1CCE11726DD9900587077 /* charon-xpc-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "charon-xpc-Info.plist"; sourceTree = "<group>"; }; + 5BD1CCEA1727CCA400587077 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README.md; sourceTree = "<group>"; }; + 5BD1CCEC1727D7AF00587077 /* ServiceManagement.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ServiceManagement.framework; path = System/Library/Frameworks/ServiceManagement.framework; sourceTree = SDKROOT; }; + 5BD1CCF21727DE3E00587077 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; + 5BF60F621743C57500E5D608 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 5BD1CCCE1726DB4000587077 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5BF60F631743C57500E5D608 /* SystemConfiguration.framework in Frameworks */, + 5BF60F31173405A000E5D608 /* CoreFoundation.framework in Frameworks */, + 5BF60F33173405AC00E5D608 /* Security.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 5BD1CCA11726DB0100587077 = { + isa = PBXGroup; + children = ( + 5BD1CCEA1727CCA400587077 /* README.md */, + 5BD1CCD51726DB4000587077 /* charon-xpc */, + 5BD1CCAF1726DB0100587077 /* Frameworks */, + 5BD1CCAD1726DB0100587077 /* Products */, + ); + sourceTree = "<group>"; + }; + 5BD1CCAD1726DB0100587077 /* Products */ = { + isa = PBXGroup; + children = ( + 5BD1CCD11726DB4000587077 /* org.strongswan.charon-xpc */, + ); + name = Products; + sourceTree = "<group>"; + }; + 5BD1CCAF1726DB0100587077 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 5BF60F621743C57500E5D608 /* SystemConfiguration.framework */, + 5BD1CCF21727DE3E00587077 /* Security.framework */, + 5BD1CCEC1727D7AF00587077 /* ServiceManagement.framework */, + 5BD1CCD31726DB4000587077 /* CoreFoundation.framework */, + ); + name = Frameworks; + sourceTree = "<group>"; + }; + 5BD1CCD51726DB4000587077 /* charon-xpc */ = { + isa = PBXGroup; + children = ( + 5BD1CCD61726DB4000587077 /* charon-xpc.c */, + 5BD1CCE01726DCD000587077 /* charon-xpc-Launchd.plist */, + 5BD1CCE11726DD9900587077 /* charon-xpc-Info.plist */, + 5B74984C172AA3550041971E /* xpc_dispatch.c */, + 5B74984E172AA3670041971E /* xpc_dispatch.h */, + 5B74989017311AFC0041971E /* xpc_channels.h */, + 5B74989117311B200041971E /* xpc_channels.c */, + 5B7498B7173275D10041971E /* xpc_logger.c */, + 5B7498B9173275DD0041971E /* xpc_logger.h */, + ); + path = "charon-xpc"; + sourceTree = "<group>"; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 5BD1CCD01726DB4000587077 /* charon-xpc */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5BD1CCDA1726DB4000587077 /* Build configuration list for PBXNativeTarget "charon-xpc" */; + buildPhases = ( + 5BD1CCCD1726DB4000587077 /* Sources */, + 5BD1CCCE1726DB4000587077 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "charon-xpc"; + productName = "charon-xpc"; + productReference = 5BD1CCD11726DB4000587077 /* org.strongswan.charon-xpc */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 5BD1CCA31726DB0100587077 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0450; + ORGANIZATIONNAME = "revosec AG"; + }; + buildConfigurationList = 5BD1CCA61726DB0100587077 /* Build configuration list for PBXProject "strongSwan" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 5BD1CCA11726DB0100587077; + productRefGroup = 5BD1CCAD1726DB0100587077 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 5BD1CCD01726DB4000587077 /* charon-xpc */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 5BD1CCCD1726DB4000587077 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5BD1CCD71726DB4000587077 /* charon-xpc.c in Sources */, + 5B74989217311B200041971E /* xpc_channels.c in Sources */, + 5BF60F3E1734070A00E5D608 /* xpc_dispatch.c in Sources */, + 5B7498B8173275D10041971E /* xpc_logger.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 5BD1CCC81726DB0200587077 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD_64_BIT)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.8; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = Debug; + }; + 5BD1CCC91726DB0200587077 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD_64_BIT)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.8; + SDKROOT = macosx; + }; + name = Release; + }; + 5BD1CCDB1726DB4000587077 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = "Joe Developer"; + GCC_WARN_64_TO_32_BIT_CONVERSION = NO; + GCC_WARN_ABOUT_POINTER_SIGNEDNESS = NO; + HEADER_SEARCH_PATHS = ( + /usr/include, + ../../libstrongswan, + ../../libcharon, + ../../libhydra, + /opt/local/include, + ); + INFOPLIST_FILE = "charon-xpc/charon-xpc-Info.plist"; + INSTALL_PATH = /; + LIBRARY_SEARCH_PATHS = ( + /usr/lib, + ../../libstrongswan/.libs, + ../../libcharon/.libs, + ../../libhydra/.libs, + /opt/local/lib, + ); + OTHER_CFLAGS = ( + "-include", + ../../../config.h, + "-DVSTR_COMPILE_INLINE=0", + ); + OTHER_LDFLAGS = ( + "-lcrypto", + /opt/local/lib/libvstr.a, + "-force_load", + ../../libstrongswan/.libs/libstrongswan.a, + "-force_load", + ../../libhydra/.libs/libhydra.a, + "-force_load", + ../../libcharon/.libs/libcharon.a, + "-sectcreate", + __TEXT, + __info_plist, + "charon-xpc/charon-xpc-Info.plist", + "-sectcreate", + __TEXT, + __launchd_plist, + "charon-xpc/charon-xpc-Launchd.plist", + ); + PRODUCT_NAME = "org.strongswan.charon-xpc"; + PROVISIONING_PROFILE = ""; + STRIP_STYLE = "non-global"; + }; + name = Debug; + }; + 5BD1CCDC1726DB4000587077 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = "Joe Developer"; + COPY_PHASE_STRIP = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = NO; + GCC_WARN_ABOUT_POINTER_SIGNEDNESS = NO; + HEADER_SEARCH_PATHS = ( + /usr/include, + ../../libstrongswan, + ../../libcharon, + ../../libhydra, + /opt/local/include, + ); + INFOPLIST_FILE = "charon-xpc/charon-xpc-Info.plist"; + INSTALL_PATH = /; + LIBRARY_SEARCH_PATHS = ( + /usr/lib, + ../../libstrongswan/.libs, + ../../libcharon/.libs, + ../../libhydra/.libs, + /opt/local/lib, + ); + OTHER_CFLAGS = ( + "-include", + ../../../config.h, + "-DVSTR_COMPILE_INLINE=0", + ); + OTHER_LDFLAGS = ( + "-lcrypto", + /opt/local/lib/libvstr.a, + "-force_load", + ../../libstrongswan/.libs/libstrongswan.a, + "-force_load", + ../../libhydra/.libs/libhydra.a, + "-force_load", + ../../libcharon/.libs/libcharon.a, + "-sectcreate", + __TEXT, + __info_plist, + "charon-xpc/charon-xpc-Info.plist", + "-sectcreate", + __TEXT, + __launchd_plist, + "charon-xpc/charon-xpc-Launchd.plist", + ); + PRODUCT_NAME = "org.strongswan.charon-xpc"; + PROVISIONING_PROFILE = ""; + STRIP_STYLE = "non-global"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 5BD1CCA61726DB0100587077 /* Build configuration list for PBXProject "strongSwan" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5BD1CCC81726DB0200587077 /* Debug */, + 5BD1CCC91726DB0200587077 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 5BD1CCDA1726DB4000587077 /* Build configuration list for PBXNativeTarget "charon-xpc" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5BD1CCDB1726DB4000587077 /* Debug */, + 5BD1CCDC1726DB4000587077 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 5BD1CCA31726DB0100587077 /* Project object */; +} diff --git a/src/libcharon/bus/listeners/sys_logger.c b/src/libcharon/bus/listeners/sys_logger.c index 82e2c8e4c..4aeb1c048 100644 --- a/src/libcharon/bus/listeners/sys_logger.c +++ b/src/libcharon/bus/listeners/sys_logger.c @@ -173,6 +173,7 @@ sys_logger_t *sys_logger_create(int facility) ); set_level(this, DBG_ANY, LEVEL_SILENT); + setlogmask(LOG_UPTO(LOG_INFO)); return &this->public; } diff --git a/src/libstrongswan/Makefile.am b/src/libstrongswan/Makefile.am index bde5f710a..82d2159ce 100644 --- a/src/libstrongswan/Makefile.am +++ b/src/libstrongswan/Makefile.am @@ -423,6 +423,13 @@ if MONOLITHIC endif endif +if USE_KEYCHAIN + SUBDIRS += plugins/keychain +if MONOLITHIC + libstrongswan_la_LIBADD += plugins/keychain/libstrongswan-keychain.la +endif +endif + if USE_PKCS11 SUBDIRS += plugins/pkcs11 if MONOLITHIC diff --git a/src/libstrongswan/collections/hashtable.c b/src/libstrongswan/collections/hashtable.c index d181d8ec8..1003aa0fa 100644 --- a/src/libstrongswan/collections/hashtable.c +++ b/src/libstrongswan/collections/hashtable.c @@ -16,6 +16,8 @@ #include "hashtable.h" +#include <utils/chunk.h> + /** The maximum capacity of the hash table (MUST be a power of 2) */ #define MAX_CAPACITY (1 << 30) @@ -146,9 +148,40 @@ struct private_enumerator_t { * previous pair (used by remove_at) */ pair_t *prev; - }; +/* + * See header. + */ +u_int hashtable_hash_ptr(void *key) +{ + return chunk_hash(chunk_from_thing(key)); +} + +/* + * See header. + */ +u_int hashtable_hash_str(void *key) +{ + return chunk_hash(chunk_from_str((char*)key)); +} + +/* + * See header. + */ +bool hashtable_equals_ptr(void *key, void *other_key) +{ + return key == other_key; +} + +/* + * See header. + */ +bool hashtable_equals_str(void *key, void *other_key) +{ + return streq(key, other_key); +} + /** * This function returns the next-highest power of two for the given number. * The algorithm works by setting all bits on the right-hand side of the most @@ -441,4 +474,3 @@ hashtable_t *hashtable_create(hashtable_hash_t hash, hashtable_equals_t equals, return &this->public; } - diff --git a/src/libstrongswan/collections/hashtable.h b/src/libstrongswan/collections/hashtable.h index e38850ded..520a86c90 100644 --- a/src/libstrongswan/collections/hashtable.h +++ b/src/libstrongswan/collections/hashtable.h @@ -34,6 +34,22 @@ typedef struct hashtable_t hashtable_t; typedef u_int (*hashtable_hash_t)(void *key); /** + * Hashtable hash function calculation the hash solely based on the key pointer. + * + * @param key key to hash + * @return hash of key + */ +u_int hashtable_hash_ptr(void *key); + +/** + * Hashtable hash function calculation the hash for char* keys. + * + * @param key key to hash, a char* + * @return hash of key + */ +u_int hashtable_hash_str(void *key); + +/** * Prototype for a function that compares the two keys for equality. * * @param key first key (the one we are looking for) @@ -43,6 +59,24 @@ typedef u_int (*hashtable_hash_t)(void *key); typedef bool (*hashtable_equals_t)(void *key, void *other_key); /** + * Hashtable equals function comparing pointers. + * + * @param key key to compare + * @param other_key other key to compare + * @return TRUE if key == other_key + */ +bool hashtable_equals_ptr(void *key, void *other_key); + +/** + * Hashtable equals function comparing char* keys. + * + * @param key key to compare + * @param other_key other key to compare + * @return TRUE if streq(key, other_key) + */ +bool hashtable_equals_str(void *key, void *other_key); + +/** * Class implementing a hash table. * * General purpose hash table. This hash table is not synchronized. @@ -121,7 +155,6 @@ struct hashtable_t { * Destroys a hash table object. */ void (*destroy) (hashtable_t *this); - }; /** diff --git a/src/libstrongswan/credentials/credential_manager.c b/src/libstrongswan/credentials/credential_manager.c index f4cd9b9e6..fa255551b 100644 --- a/src/libstrongswan/credentials/credential_manager.c +++ b/src/libstrongswan/credentials/credential_manager.c @@ -378,8 +378,8 @@ METHOD(credential_manager_t, get_shared, shared_key_t*, identification_t *me, identification_t *other) { shared_key_t *current, *found = NULL; - id_match_t *best_me = ID_MATCH_NONE, *best_other = ID_MATCH_NONE; - id_match_t *match_me, *match_other; + id_match_t best_me = ID_MATCH_NONE, best_other = ID_MATCH_NONE; + id_match_t match_me, match_other; enumerator_t *enumerator; enumerator = create_shared_enumerator(this, type, me, other); @@ -393,6 +393,10 @@ METHOD(credential_manager_t, get_shared, shared_key_t*, best_me = match_me; best_other = match_other; } + if (best_me == ID_MATCH_PERFECT && best_other == ID_MATCH_PERFECT) + { + break; + } } enumerator->destroy(enumerator); return found; diff --git a/src/libstrongswan/plugins/keychain/Makefile.am b/src/libstrongswan/plugins/keychain/Makefile.am new file mode 100644 index 000000000..508a4b024 --- /dev/null +++ b/src/libstrongswan/plugins/keychain/Makefile.am @@ -0,0 +1,17 @@ + +INCLUDES = -I$(top_srcdir)/src/libstrongswan + +AM_CFLAGS = -rdynamic + +if MONOLITHIC +noinst_LTLIBRARIES = libstrongswan-keychain.la +else +plugin_LTLIBRARIES = libstrongswan-keychain.la +endif + +libstrongswan_keychain_la_SOURCES = \ + keychain_plugin.h keychain_plugin.c \ + keychain_creds.h keychain_creds.c + +libstrongswan_keychain_la_LDFLAGS = -module -avoid-version \ + -framework Security -framework CoreFoundation diff --git a/src/libstrongswan/plugins/keychain/keychain_creds.c b/src/libstrongswan/plugins/keychain/keychain_creds.c new file mode 100644 index 000000000..ddcc7a461 --- /dev/null +++ b/src/libstrongswan/plugins/keychain/keychain_creds.c @@ -0,0 +1,203 @@ +/* + * 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 <http://www.fsf.org/copyleft/gpl.txt>. + * + * 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 <utils/debug.h> +#include <credentials/sets/mem_cred.h> +#include <processing/jobs/callback_job.h> + +#include <Security/Security.h> + +/** + * 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; + + set = mem_cred_create(); + + DBG1(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) + { + DBG1(DBG_CFG, " loaded '%Y'", cert->get_subject(cert)); + set->add_cert(set, TRUE, cert); + } + SecKeychainItemFreeAttributesAndData(NULL, data); + } + CFRelease(item); + } + CFRelease(search); + } + CFRelease(keychain); + } + 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 cancelled */ + 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; +} diff --git a/src/libstrongswan/plugins/keychain/keychain_creds.h b/src/libstrongswan/plugins/keychain/keychain_creds.h new file mode 100644 index 000000000..64a2ededd --- /dev/null +++ b/src/libstrongswan/plugins/keychain/keychain_creds.h @@ -0,0 +1,44 @@ +/* + * 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 <http://www.fsf.org/copyleft/gpl.txt>. + * + * 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. + */ + +/** + * @defgroup keychain_creds keychain_creds + * @{ @ingroup keychain + */ + +#ifndef KEYCHAIN_CREDS_H_ +#define KEYCHAIN_CREDS_H_ + +typedef struct keychain_creds_t keychain_creds_t; + +#include <credentials/credential_manager.h> + +/** + * Credential set using OS X Keychain Services. + */ +struct keychain_creds_t { + + /** + * Destroy a keychain_creds_t. + */ + void (*destroy)(keychain_creds_t *this); +}; + +/** + * Create a keychain_creds instance. + */ +keychain_creds_t *keychain_creds_create(); + +#endif /** KEYCHAIN_CREDS_H_ @}*/ diff --git a/src/libstrongswan/plugins/keychain/keychain_plugin.c b/src/libstrongswan/plugins/keychain/keychain_plugin.c new file mode 100644 index 000000000..6112afaa8 --- /dev/null +++ b/src/libstrongswan/plugins/keychain/keychain_plugin.c @@ -0,0 +1,98 @@ +/* + * 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 <http://www.fsf.org/copyleft/gpl.txt>. + * + * 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_plugin.h" +#include "keychain_creds.h" + +#include <library.h> + +typedef struct private_keychain_plugin_t private_keychain_plugin_t; + +/** + * private data of keychain_plugin + */ +struct private_keychain_plugin_t { + + /** + * public functions + */ + keychain_plugin_t public; + + /** + * System level Keychain Services credential set + */ + keychain_creds_t *creds; +}; + +METHOD(plugin_t, get_name, char*, + private_keychain_plugin_t *this) +{ + return "keychain"; +} + +/** + * Load/unload certificates from Keychain. + */ +static bool load_creds(private_keychain_plugin_t *this, + plugin_feature_t *feature, bool reg, void *data) +{ + if (reg) + { + this->creds = keychain_creds_create(); + } + else + { + this->creds->destroy(this->creds); + } + return TRUE; +} + +METHOD(plugin_t, get_features, int, + private_keychain_plugin_t *this, plugin_feature_t *features[]) +{ + static plugin_feature_t f[] = { + PLUGIN_CALLBACK((plugin_feature_callback_t)load_creds, NULL), + PLUGIN_PROVIDE(CUSTOM, "keychain"), + PLUGIN_DEPENDS(CERT_DECODE, CERT_X509), + }; + *features = f; + return countof(f); +} + +METHOD(plugin_t, destroy, void, + private_keychain_plugin_t *this) +{ + free(this); +} + +/* + * see header file + */ +plugin_t *keychain_plugin_create() +{ + private_keychain_plugin_t *this; + + INIT(this, + .public = { + .plugin = { + .get_name = _get_name, + .get_features = _get_features, + .destroy = _destroy, + }, + }, + ); + + return &this->public.plugin; +} diff --git a/src/libstrongswan/plugins/keychain/keychain_plugin.h b/src/libstrongswan/plugins/keychain/keychain_plugin.h new file mode 100644 index 000000000..482f173c3 --- /dev/null +++ b/src/libstrongswan/plugins/keychain/keychain_plugin.h @@ -0,0 +1,42 @@ +/* + * 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 <http://www.fsf.org/copyleft/gpl.txt>. + * + * 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. + */ + +/** + * @defgroup keychain keychain + * @ingroup plugins + * + * @defgroup keychain_plugin keychain_plugin + * @{ @ingroup keychain + */ + +#ifndef KEYCHAIN_PLUGIN_H_ +#define KEYCHAIN_PLUGIN_H_ + +#include <plugins/plugin.h> + +typedef struct keychain_plugin_t keychain_plugin_t; + +/** + * Plugin providing OS X Keychain Services support. + */ +struct keychain_plugin_t { + + /** + * Implements plugin interface, + */ + plugin_t plugin; +}; + +#endif /** KEYCHAIN_PLUGIN_H_ @}*/ diff --git a/src/libstrongswan/plugins/openssl/openssl_x509.c b/src/libstrongswan/plugins/openssl/openssl_x509.c index c98e8055d..24b12d50c 100644 --- a/src/libstrongswan/plugins/openssl/openssl_x509.c +++ b/src/libstrongswan/plugins/openssl/openssl_x509.c @@ -679,6 +679,41 @@ static bool parse_keyUsage_ext(private_openssl_x509_t *this, } /** + * Parse ExtendedKeyUsage + */ +static bool parse_extKeyUsage_ext(private_openssl_x509_t *this, + X509_EXTENSION *ext) +{ + EXTENDED_KEY_USAGE *usage; + int i; + + usage = X509V3_EXT_d2i(ext); + if (usage) + { + for (i = 0; i < sk_ASN1_OBJECT_num(usage); i++) + { + switch (OBJ_obj2nid(sk_ASN1_OBJECT_value(usage, i))) + { + case NID_server_auth: + this->flags |= X509_SERVER_AUTH; + break; + case NID_client_auth: + this->flags |= X509_CLIENT_AUTH; + break; + case NID_OCSP_sign: + this->flags |= X509_OCSP_SIGNER; + break; + default: + break; + } + } + sk_ASN1_OBJECT_pop_free(usage, ASN1_OBJECT_free); + return TRUE; + } + return FALSE; +} + +/** * Parse CRL distribution points */ static bool parse_crlDistributionPoints_ext(private_openssl_x509_t *this, @@ -963,6 +998,9 @@ static bool parse_extensions(private_openssl_x509_t *this) case NID_key_usage: ok = parse_keyUsage_ext(this, ext); break; + case NID_ext_key_usage: + ok = parse_extKeyUsage_ext(this, ext); + break; case NID_crl_distribution_points: ok = parse_crlDistributionPoints_ext(this, ext); break; @@ -977,7 +1015,12 @@ static bool parse_extensions(private_openssl_x509_t *this) "libstrongswan.x509.enforce_critical", TRUE); if (!ok) { - DBG1(DBG_LIB, "found unsupported critical X.509 extension"); + char buf[80] = ""; + + OBJ_obj2txt(buf, sizeof(buf), + X509_EXTENSION_get_object(ext), 0); + DBG1(DBG_LIB, "found unsupported critical X.509 " + "extension: %s", buf); } break; } @@ -991,38 +1034,6 @@ static bool parse_extensions(private_openssl_x509_t *this) } /** - * Parse ExtendedKeyUsage - */ -static void parse_extKeyUsage(private_openssl_x509_t *this) -{ - EXTENDED_KEY_USAGE *usage; - int i; - - usage = X509_get_ext_d2i(this->x509, NID_ext_key_usage, NULL, NULL); - if (usage) - { - for (i = 0; i < sk_ASN1_OBJECT_num(usage); i++) - { - switch (OBJ_obj2nid(sk_ASN1_OBJECT_value(usage, i))) - { - case NID_server_auth: - this->flags |= X509_SERVER_AUTH; - break; - case NID_client_auth: - this->flags |= X509_CLIENT_AUTH; - break; - case NID_OCSP_sign: - this->flags |= X509_OCSP_SIGNER; - break; - default: - break; - } - } - sk_ASN1_OBJECT_pop_free(usage, ASN1_OBJECT_free); - } -} - -/** * Parse a DER encoded x509 certificate */ static bool parse_certificate(private_openssl_x509_t *this) @@ -1088,7 +1099,6 @@ static bool parse_certificate(private_openssl_x509_t *this) { return FALSE; } - parse_extKeyUsage(this); hasher = lib->crypto->create_hasher(lib->crypto, HASH_SHA1); if (!hasher || !hasher->allocate_hash(hasher, this->encoding, &this->hash)) diff --git a/src/libstrongswan/threading/thread.c b/src/libstrongswan/threading/thread.c index d6d98d1ef..eb167d6a4 100644 --- a/src/libstrongswan/threading/thread.c +++ b/src/libstrongswan/threading/thread.c @@ -341,7 +341,20 @@ thread_t *thread_create(thread_main_t main, void *arg) */ thread_t *thread_current() { - return current_thread->get(current_thread); + private_thread_t *this; + + this = (private_thread_t*)current_thread->get(current_thread); + if (!this) + { + this = thread_create_internal(); + + id_mutex->lock(id_mutex); + this->id = next_id++; + id_mutex->unlock(id_mutex); + + current_thread->set(current_thread, (void*)this); + } + return &this->public; } /** |