summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorVasily Kulikov <segoon@openwall.com>2015-02-25 19:07:18 +0300
committerGert Doering <gert@greenie.muc.de>2015-04-13 20:36:11 +0200
commit39e3d336d4eeab847a3395ddeb430e0a9ca387b9 (patch)
tree59a97e3e7fe699286562ebe76be3f11bdcc2f97c /src
parentec2fbf374f018366c18644d271cd4d793d04244b (diff)
downloadopenvpn-39e3d336d4eeab847a3395ddeb430e0a9ca387b9.tar.gz
openvpn-39e3d336d4eeab847a3395ddeb430e0a9ca387b9.tar.xz
openvpn-39e3d336d4eeab847a3395ddeb430e0a9ca387b9.zip
Mac OS X Keychain management client
This patch adds support for using certificates stored in the Mac OSX Keychain to authenticate with the OpenVPN server. This works with certificates stored on the computer as well as certificates on hardware tokens that support Apple's tokend interface. The patch is based on the Windows Crypto API certificate functionality that currently exists in OpenVPN. This patch version implements management client which handles RSA-SIGN command for RSA offloading. Also it handles new 'NEED-CERTIFICATE' request to pass a certificate from the keychain to OpenVPN. OpenVPN itself gets new 'NEED-CERTIFICATE" command which is called when --management-external-cert is used. It is implemented as a multiline command very similar to an existing 'RSA-SIGN' command. The patch is against commit 3341a98c2852d1d0c1eafdc70a3bdb218ec29049. v4: - added '--management-external-cert' argument - keychain-mcd now parses NEED-CERTIFICATE argument if 'auto' is passed as cmdline's identity template - fixed typo in help output option name - added '--management-external-cert' info in openvpn(8) manpage - added 'certificate' command documentation into doc/management-notes.txt v3: - used new 'NEED-CERTIFICATE' command for certificate data request instead of 'NEED-OK' - improved option checking - improved invalid certificate selection string handling - added man page for keychain-mcd - handle INFO, FATAL commands from openvpn and show them to user * ACK from Arne Schwabe for OpenVPN part * ACK from James based on Arne's testing v2 (http://sourceforge.net/p/openvpn/mailman/message/33225603/): - used management interface to communicate with OpenVPN process v1 (http://sourceforge.net/p/openvpn/mailman/message/33125844/): - used RSA_METHOD to extend openvpn itself Signed-off-by: Vasily Kulikov <segoon@openwall.com> -- Acked-by: Arne Schwabe <arne@rfc2549.org> Message-Id: <20150225160718.GA6306@cachalot> URL: http://article.gmane.org/gmane.network.openvpn.devel/9486 Signed-off-by: Gert Doering <gert@greenie.muc.de>
Diffstat (limited to 'src')
-rw-r--r--src/openvpn/buffer.c13
-rw-r--r--src/openvpn/buffer.h1
-rw-r--r--src/openvpn/manage.c153
-rw-r--r--src/openvpn/manage.h5
-rw-r--r--src/openvpn/options.c22
-rw-r--r--src/openvpn/options.h1
-rw-r--r--src/openvpn/ssl.c17
7 files changed, 177 insertions, 35 deletions
diff --git a/src/openvpn/buffer.c b/src/openvpn/buffer.c
index 46f874b..421d60e 100644
--- a/src/openvpn/buffer.c
+++ b/src/openvpn/buffer.c
@@ -1066,8 +1066,10 @@ buffer_list_peek (struct buffer_list *ol)
}
void
-buffer_list_aggregate (struct buffer_list *bl, const size_t max)
+buffer_list_aggregate_separator (struct buffer_list *bl, const size_t max, const char *sep)
{
+ int sep_len = strlen(sep);
+
if (bl->head)
{
struct buffer_entry *more = bl->head;
@@ -1075,7 +1077,7 @@ buffer_list_aggregate (struct buffer_list *bl, const size_t max)
int count = 0;
for (count = 0; more && size <= max; ++count)
{
- size += BLEN(&more->buf);
+ size += BLEN(&more->buf) + sep_len;
more = more->next;
}
@@ -1092,6 +1094,7 @@ buffer_list_aggregate (struct buffer_list *bl, const size_t max)
{
struct buffer_entry *next = e->next;
buf_copy (&f->buf, &e->buf);
+ buf_write(&f->buf, sep, sep_len);
free_buf (&e->buf);
free (e);
e = next;
@@ -1105,6 +1108,12 @@ buffer_list_aggregate (struct buffer_list *bl, const size_t max)
}
void
+buffer_list_aggregate (struct buffer_list *bl, const size_t max)
+{
+ buffer_list_aggregate_separator(bl, max, "");
+}
+
+void
buffer_list_pop (struct buffer_list *ol)
{
if (ol && ol->head)
diff --git a/src/openvpn/buffer.h b/src/openvpn/buffer.h
index 7469da6..5695f64 100644
--- a/src/openvpn/buffer.h
+++ b/src/openvpn/buffer.h
@@ -931,6 +931,7 @@ void buffer_list_advance (struct buffer_list *ol, int n);
void buffer_list_pop (struct buffer_list *ol);
void buffer_list_aggregate (struct buffer_list *bl, const size_t max);
+void buffer_list_aggregate_separator (struct buffer_list *bl, const size_t max, const char *sep);
struct buffer_list *buffer_list_file (const char *fn, int max_line_len);
#endif /* BUFFER_H */
diff --git a/src/openvpn/manage.c b/src/openvpn/manage.c
index e59776d..4f0945c 100644
--- a/src/openvpn/manage.c
+++ b/src/openvpn/manage.c
@@ -113,6 +113,8 @@ man_help ()
#ifdef MANAGMENT_EXTERNAL_KEY
msg (M_CLIENT, "rsa-sig : Enter an RSA signature in response to >RSA_SIGN challenge");
msg (M_CLIENT, " Enter signature base64 on subsequent lines followed by END");
+ msg (M_CLIENT, "certificate : Enter a client certificate in response to >NEED-CERT challenge");
+ msg (M_CLIENT, " Enter certificate base64 on subsequent lines followed by END");
#endif
msg (M_CLIENT, "signal s : Send signal s to daemon,");
msg (M_CLIENT, " s = SIGHUP|SIGTERM|SIGUSR1|SIGUSR2.");
@@ -868,6 +870,12 @@ in_extra_dispatch (struct management *man)
man->connection.ext_key_input = man->connection.in_extra;
man->connection.in_extra = NULL;
return;
+ case IEC_CERTIFICATE:
+ man->connection.ext_cert_state = EKS_READY;
+ buffer_list_free (man->connection.ext_cert_input);
+ man->connection.ext_cert_input = man->connection.in_extra;
+ man->connection.in_extra = NULL;
+ return;
#endif
}
in_extra_reset (&man->connection, IER_RESET);
@@ -1030,6 +1038,20 @@ man_rsa_sig (struct management *man)
msg (M_CLIENT, "ERROR: The rsa-sig command is not currently available");
}
+static void
+man_certificate (struct management *man)
+{
+ struct man_connection *mc = &man->connection;
+ if (mc->ext_cert_state == EKS_SOLICIT)
+ {
+ mc->ext_cert_state = EKS_INPUT;
+ mc->in_extra_cmd = IEC_CERTIFICATE;
+ in_extra_reset (mc, IER_NEW);
+ }
+ else
+ msg (M_CLIENT, "ERROR: The certificate command is not currently available");
+}
+
#endif
static void
@@ -1311,6 +1333,10 @@ man_dispatch_command (struct management *man, struct status_output *so, const ch
{
man_rsa_sig (man);
}
+ else if (streq (p[0], "certificate"))
+ {
+ man_certificate (man);
+ }
#endif
#ifdef ENABLE_PKCS11
else if (streq (p[0], "pkcs11-id-count"))
@@ -3097,15 +3123,14 @@ management_query_user_pass (struct management *man,
#ifdef MANAGMENT_EXTERNAL_KEY
-char * /* returns allocated base64 signature */
-management_query_rsa_sig (struct management *man,
- const char *b64_data)
+int
+management_query_multiline (struct management *man,
+ const char *b64_data, const char *prompt, const char *cmd, int *state, struct buffer_list **input)
{
struct gc_arena gc = gc_new ();
- char *ret = NULL;
+ int ret = 0;
volatile int signal_received = 0;
struct buffer alert_msg = clear_buf();
- struct buffer *buf;
const bool standalone_disabled_save = man->persist.standalone_disabled;
struct man_connection *mc = &man->connection;
@@ -3114,10 +3139,15 @@ management_query_rsa_sig (struct management *man,
man->persist.standalone_disabled = false; /* This is so M_CLIENT messages will be correctly passed through msg() */
man->persist.special_state_msg = NULL;
- mc->ext_key_state = EKS_SOLICIT;
+ *state = EKS_SOLICIT;
- alert_msg = alloc_buf_gc (strlen(b64_data)+64, &gc);
- buf_printf (&alert_msg, ">RSA_SIGN:%s", b64_data);
+ if (b64_data) {
+ alert_msg = alloc_buf_gc (strlen(b64_data)+strlen(prompt)+3, &gc);
+ buf_printf (&alert_msg, ">%s:%s", prompt, b64_data);
+ } else {
+ alert_msg = alloc_buf_gc (strlen(prompt)+3, &gc);
+ buf_printf (&alert_msg, ">%s", prompt);
+ }
man_wait_for_client_connection (man, &signal_received, 0, MWCC_OTHER_WAIT);
@@ -3135,40 +3165,107 @@ management_query_rsa_sig (struct management *man,
man_check_for_signals (&signal_received);
if (signal_received)
goto done;
- } while (mc->ext_key_state != EKS_READY);
+ } while (*state != EKS_READY);
- if (buffer_list_defined(mc->ext_key_input))
- {
- buffer_list_aggregate (mc->ext_key_input, 2048);
- buf = buffer_list_peek (mc->ext_key_input);
- if (buf && BLEN(buf) > 0)
- {
- ret = (char *) malloc(BLEN(buf)+1);
- check_malloc_return(ret);
- memcpy(ret, buf->data, BLEN(buf));
- ret[BLEN(buf)] = '\0';
- }
- }
+ ret = 1;
}
done:
- if (mc->ext_key_state == EKS_READY && ret)
- msg (M_CLIENT, "SUCCESS: rsa-sig command succeeded");
- else if (mc->ext_key_state == EKS_INPUT || mc->ext_key_state == EKS_READY)
- msg (M_CLIENT, "ERROR: rsa-sig command failed");
+ if (*state == EKS_READY && ret)
+ msg (M_CLIENT, "SUCCESS: %s command succeeded", cmd);
+ else if (*state == EKS_INPUT || *state == EKS_READY)
+ msg (M_CLIENT, "ERROR: %s command failed", cmd);
/* revert state */
man->persist.standalone_disabled = standalone_disabled_save;
man->persist.special_state_msg = NULL;
in_extra_reset (mc, IER_RESET);
- mc->ext_key_state = EKS_UNDEF;
- buffer_list_free (mc->ext_key_input);
- mc->ext_key_input = NULL;
+ *state = EKS_UNDEF;
gc_free (&gc);
return ret;
}
+char * /* returns allocated base64 signature */
+management_query_multiline_flatten_newline (struct management *man,
+ const char *b64_data, const char *prompt, const char *cmd, int *state, struct buffer_list **input)
+{
+ int ok;
+ char *result = NULL;
+ struct buffer *buf;
+
+ ok = management_query_multiline(man, b64_data, prompt, cmd, state, input);
+ if (ok && buffer_list_defined(*input))
+ {
+ buffer_list_aggregate_separator (*input, 10000, "\n");
+ buf = buffer_list_peek (*input);
+ if (buf && BLEN(buf) > 0)
+ {
+ result = (char *) malloc(BLEN(buf)+1);
+ check_malloc_return(result);
+ memcpy(result, buf->data, BLEN(buf));
+ result[BLEN(buf)] = '\0';
+ }
+ }
+
+ buffer_list_free (*input);
+ *input = NULL;
+
+ return result;
+}
+
+char * /* returns allocated base64 signature */
+management_query_multiline_flatten (struct management *man,
+ const char *b64_data, const char *prompt, const char *cmd, int *state, struct buffer_list **input)
+{
+ int ok;
+ char *result = NULL;
+ struct buffer *buf;
+
+ ok = management_query_multiline(man, b64_data, prompt, cmd, state, input);
+ if (ok && buffer_list_defined(*input))
+ {
+ buffer_list_aggregate (*input, 2048);
+ buf = buffer_list_peek (*input);
+ if (buf && BLEN(buf) > 0)
+ {
+ result = (char *) malloc(BLEN(buf)+1);
+ check_malloc_return(result);
+ memcpy(result, buf->data, BLEN(buf));
+ result[BLEN(buf)] = '\0';
+ }
+ }
+
+ buffer_list_free (*input);
+ *input = NULL;
+
+ return result;
+}
+
+char * /* returns allocated base64 signature */
+management_query_rsa_sig (struct management *man,
+ const char *b64_data)
+{
+ return management_query_multiline_flatten(man, b64_data, "RSA_SIGN", "rsa-sign",
+ &man->connection.ext_key_state, &man->connection.ext_key_input);
+}
+
+
+char* management_query_cert (struct management *man, const char *cert_name)
+{
+ const char prompt_1[] = "NEED-CERTIFICATE:";
+ struct buffer buf_prompt = alloc_buf(strlen(cert_name) + 20);
+ buf_write(&buf_prompt, prompt_1, strlen(prompt_1));
+ buf_write(&buf_prompt, cert_name, strlen(cert_name)+1); // +1 for \0
+
+ char *result;
+ result = management_query_multiline_flatten_newline(management,
+ NULL, (char*)buf_bptr(&buf_prompt), "certificate",
+ &man->connection.ext_cert_state, &man->connection.ext_cert_input);
+ free_buf(&buf_prompt);
+ return result;
+}
+
#endif
/*
diff --git a/src/openvpn/manage.h b/src/openvpn/manage.h
index 1c8dda6..8d6e87e 100644
--- a/src/openvpn/manage.h
+++ b/src/openvpn/manage.h
@@ -268,6 +268,7 @@ struct man_connection {
# define IEC_CLIENT_AUTH 1
# define IEC_CLIENT_PF 2
# define IEC_RSA_SIGN 3
+# define IEC_CERTIFICATE 4
int in_extra_cmd;
struct buffer_list *in_extra;
#ifdef MANAGEMENT_DEF_AUTH
@@ -281,6 +282,8 @@ struct man_connection {
# define EKS_READY 3
int ext_key_state;
struct buffer_list *ext_key_input;
+ int ext_cert_state;
+ struct buffer_list *ext_cert_input;
#endif
#endif
struct event_set *es;
@@ -338,6 +341,7 @@ struct management *management_init (void);
#define MF_UP_DOWN (1<<10)
#define MF_QUERY_REMOTE (1<<11)
#define MF_QUERY_PROXY (1<<12)
+#define MF_EXTERNAL_CERT (1<<13)
bool management_open (struct management *man,
const char *addr,
@@ -420,6 +424,7 @@ void management_learn_addr (struct management *management,
#ifdef MANAGMENT_EXTERNAL_KEY
char *management_query_rsa_sig (struct management *man, const char *b64_data);
+char* management_query_cert (struct management *man, const char *cert_name);
#endif
diff --git a/src/openvpn/options.c b/src/openvpn/options.c
index df9a641..e8cf06a 100644
--- a/src/openvpn/options.c
+++ b/src/openvpn/options.c
@@ -1576,6 +1576,11 @@ show_settings (const struct options *o)
SHOW_STR (ca_file);
SHOW_STR (ca_path);
SHOW_STR (dh_file);
+#ifdef MANAGMENT_EXTERNAL_KEY
+ if((o->management_flags & MF_EXTERNAL_CERT))
+ SHOW_PARM ("cert_file","EXTERNAL_CERT","%s");
+ else
+#endif
SHOW_STR (cert_file);
#ifdef MANAGMENT_EXTERNAL_KEY
@@ -2152,6 +2157,8 @@ options_postprocess_verify_ce (const struct options *options, const struct conne
#ifdef MANAGMENT_EXTERNAL_KEY
if (options->management_flags & MF_EXTERNAL_KEY)
msg(M_USAGE, "Parameter --management-external-key cannot be used when --pkcs11-provider is also specified.");
+ if (options->management_flags & MF_EXTERNAL_CERT)
+ msg(M_USAGE, "Parameter --management-external-cert cannot be used when --pkcs11-provider is also specified.");
#endif
if (options->pkcs12_file)
msg(M_USAGE, "Parameter --pkcs12 cannot be used when --pkcs11-provider is also specified.");
@@ -2183,6 +2190,8 @@ options_postprocess_verify_ce (const struct options *options, const struct conne
#ifdef MANAGMENT_EXTERNAL_KEY
if (options->management_flags & MF_EXTERNAL_KEY)
msg(M_USAGE, "Parameter --management-external-key cannot be used when --cryptoapicert is also specified.");
+ if (options->management_flags & MF_EXTERNAL_CERT)
+ msg(M_USAGE, "Parameter --management-external-cert cannot be used when --cryptoapicert is also specified.");
#endif
}
else
@@ -2200,7 +2209,9 @@ options_postprocess_verify_ce (const struct options *options, const struct conne
msg(M_USAGE, "Parameter --key cannot be used when --pkcs12 is also specified.");
#ifdef MANAGMENT_EXTERNAL_KEY
if (options->management_flags & MF_EXTERNAL_KEY)
- msg(M_USAGE, "Parameter --external-management-key cannot be used when --pkcs12 is also specified.");
+ msg(M_USAGE, "Parameter --management-external-key cannot be used when --pkcs12 is also specified.");
+ if (options->management_flags & MF_EXTERNAL_CERT)
+ msg(M_USAGE, "Parameter --management-external-cert cannot be used when --pkcs12 is also specified.");
#endif
#endif
}
@@ -2242,6 +2253,9 @@ options_postprocess_verify_ce (const struct options *options, const struct conne
}
else
{
+#ifdef MANAGMENT_EXTERNAL_KEY
+ if (!(options->management_flags & MF_EXTERNAL_CERT))
+#endif
notnull (options->cert_file, "certificate file (--cert) or PKCS#12 file (--pkcs12)");
#ifdef MANAGMENT_EXTERNAL_KEY
if (!(options->management_flags & MF_EXTERNAL_KEY))
@@ -4244,6 +4258,12 @@ add_option (struct options *options,
VERIFY_PERMISSION (OPT_P_GENERAL);
options->management_flags |= MF_EXTERNAL_KEY;
}
+ else if (streq (p[0], "management-external-cert") && p[1])
+ {
+ VERIFY_PERMISSION (OPT_P_GENERAL);
+ options->management_flags |= MF_EXTERNAL_CERT;
+ options->management_certificate = p[1];
+ }
#endif
#ifdef MANAGEMENT_DEF_AUTH
else if (streq (p[0], "management-client-auth"))
diff --git a/src/openvpn/options.h b/src/openvpn/options.h
index 7a8b21e..25b9e3c 100644
--- a/src/openvpn/options.h
+++ b/src/openvpn/options.h
@@ -370,6 +370,7 @@ struct options
/* Mask of MF_ values of manage.h */
unsigned int management_flags;
+ const char *management_certificate;
#endif
#ifdef ENABLE_PLUGIN
diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c
index 222c828..dce6c30 100644
--- a/src/openvpn/ssl.c
+++ b/src/openvpn/ssl.c
@@ -520,10 +520,19 @@ init_ssl (const struct options *options, struct tls_root_ctx *new_ctx)
}
#endif
#ifdef MANAGMENT_EXTERNAL_KEY
- else if ((options->management_flags & MF_EXTERNAL_KEY) && options->cert_file)
- {
- tls_ctx_use_external_private_key(new_ctx, options->cert_file,
- options->cert_file_inline);
+ else if ((options->management_flags & MF_EXTERNAL_KEY) &&
+ (options->cert_file || options->management_flags & MF_EXTERNAL_CERT))
+ {
+ if (options->cert_file) {
+ tls_ctx_use_external_private_key(new_ctx, options->cert_file,
+ options->cert_file_inline);
+ } else {
+ char *external_certificate = management_query_cert(management,
+ options->management_certificate);
+ tls_ctx_use_external_private_key(new_ctx, INLINE_FILE_TAG,
+ external_certificate);
+ free(external_certificate);
+ }
}
#endif
else