The patch below adds TOTP (RFC6238) one-time password support to OpenConnect. A couple notes: - I changed some of the CLI options and vpninfo structure members to make the use of "stoken" (as in libstoken) vs "software token" a bit less ambiguous. --stoken is still accepted on the command line for backwards compatibility. - openconnect_set_stoken_mode no longer accepts the use_stoken argument and instead always tries to initialize libstoken when called. This makes sense in openconnect(8), but I'm not sure how much of a concern this API change is for upstream consumers of libopenconnect. I also wasn't sure how to account for this in libopenconnect.map.in. Other than that, I think it does what it says on the box. It builds when libstoken (only) is present, libstoken and liboath are both present, and when neither library is present. I don't have a SecureID installation to actually test with, but the code changes to the libstoken path are minimal, so I think they're OK. john >From 5e3e259fda6db1085b8966495c52b792d94242ab Mon Sep 17 00:00:00 2001 From: John Morrissey <jwm at horde.net> Date: Sat, 2 Mar 2013 22:44:06 -0500 Subject: [PATCH] add oath totp support Signed-off-by: John Morrissey <jwm at horde.net> --- Makefile.am | 8 +- auth.c | 174 +++++++++++++++++++++++++++++++++++++++--------- configure.ac | 6 ++ http.c | 2 +- libopenconnect.map.in | 2 + library.c | 66 ++++++++++++++++-- main.c | 101 ++++++++++++++++++++-------- openconnect-internal.h | 21 +++++-- openconnect.8.in | 19 ++++-- openconnect.h | 10 ++- www/building.xml | 1 + www/changelog.xml | 1 + www/features.xml | 1 + 13 files changed, 328 insertions(+), 84 deletions(-) diff --git a/Makefile.am b/Makefile.am index 463df52..06aae42 100644 --- a/Makefile.am +++ b/Makefile.am @@ -14,8 +14,8 @@ man8_MANS = openconnect.8 AM_CPPFLAGS = -DLOCALEDIR="\"$(localedir)\"" openconnect_SOURCES = xml.c main.c dtls.c cstp.c mainloop.c tun.c -openconnect_CFLAGS = $(SSL_CFLAGS) $(DTLS_SSL_CFLAGS) $(LIBXML2_CFLAGS) $(LIBPROXY_CFLAGS) $(ZLIB_CFLAGS) $(LIBSTOKEN_CFLAGS) -openconnect_LDADD = libopenconnect.la $(SSL_LIBS) $(DTLS_SSL_LIBS) $(LIBXML2_LIBS) $(LIBPROXY_LIBS) $(ZLIB_LIBS) $(LIBINTL) $(LIBSTOKEN_LIBS) +openconnect_CFLAGS = $(SSL_CFLAGS) $(DTLS_SSL_CFLAGS) $(LIBXML2_CFLAGS) $(LIBPROXY_CFLAGS) $(ZLIB_CFLAGS) $(LIBSTOKEN_CFLAGS) $(LIBOATH_CFLAGS) +openconnect_LDADD = libopenconnect.la $(SSL_LIBS) $(DTLS_SSL_LIBS) $(LIBXML2_LIBS) $(LIBPROXY_LIBS) $(ZLIB_LIBS) $(LIBINTL) $(LIBSTOKEN_LIBS) $(LIBOATH_LIBS) library_srcs = ssl.c http.c auth.c library.c compat.c lib_srcs_gnutls = gnutls.c gnutls_pkcs12.c gnutls_tpm.c @@ -30,8 +30,8 @@ if OPENCONNECT_OPENSSL library_srcs += $(lib_srcs_openssl) endif libopenconnect_la_SOURCES = version.c $(library_srcs) -libopenconnect_la_CFLAGS = $(SSL_CFLAGS) $(LIBXML2_CFLAGS) $(LIBPROXY_CFLAGS) $(P11KIT_CFLAGS) $(TSS_CFLAGS) $(LIBSTOKEN_CFLAGS) -libopenconnect_la_LIBADD = $(SSL_LIBS) $(LIBXML2_LIBS) $(LIBPROXY_LIBS) $(LIBINTL) $(P11KIT_LIBS) $(TSS_LIBS) $(LIBSTOKEN_LIBS) +libopenconnect_la_CFLAGS = $(SSL_CFLAGS) $(LIBXML2_CFLAGS) $(LIBPROXY_CFLAGS) $(P11KIT_CFLAGS) $(TSS_CFLAGS) $(LIBSTOKEN_CFLAGS) $(LIBOATH_CFLAGS) +libopenconnect_la_LIBADD = $(SSL_LIBS) $(LIBXML2_LIBS) $(LIBPROXY_LIBS) $(LIBINTL) $(P11KIT_LIBS) $(TSS_LIBS) $(LIBSTOKEN_LIBS) $(LIBOATH_LIBS) if OPENBSD_LIBTOOL # OpenBSD's libtool doesn't have -version-number, but its -version-info arg # does what GNU libtool's -version-number does. Which arguably is what the diff --git a/auth.c b/auth.c index 361f656..de5d222 100644 --- a/auth.c +++ b/auth.c @@ -1,6 +1,7 @@ /* * OpenConnect (SSL + DTLS) VPN client * + * Copyright ? 2013 John Morrissey <jwm at horde.net> * Copyright ? 2008-2011 Intel Corporation. * Copyright ? 2008 Nick Andrew <nick at nick-andrew.net> * @@ -36,6 +37,10 @@ #include LIBSTOKEN_HDR #endif +#ifdef LIBOATH_HDR +#include LIBOATH_HDR +#endif + #include <libxml/parser.h> #include <libxml/tree.h> @@ -233,13 +238,15 @@ static int parse_form(struct openconnect_info *vpninfo, struct oc_auth_form *for if (!strcmp(input_type, "hidden")) { opt->type = OC_FORM_OPT_HIDDEN; opt->value = (char *)xmlGetProp(xml_node, (unsigned char *)"value"); - } else if (!strcmp(input_type, "text")) + } else if (!strcmp(input_type, "text")) { opt->type = OC_FORM_OPT_TEXT; - else if (!strcmp(input_type, "password")) { - if (vpninfo->use_stoken && !can_gen_tokencode(vpninfo, form, opt)) - opt->type = OC_FORM_OPT_STOKEN; - else + } else if (!strcmp(input_type, "password")) { + if (vpninfo->token_mode != TOKEN_MODE_NONE && + (can_gen_tokencode(vpninfo, form, opt) == 0)) { + opt->type = OC_FORM_OPT_TOKEN; + } else { opt->type = OC_FORM_OPT_PASSWORD; + } } else { vpn_progress(vpninfo, PRG_INFO, _("Unknown input type %s in form\n"), @@ -631,7 +638,7 @@ void free_auth_form(struct oc_auth_form *form) if (form->opts->type == OC_FORM_OPT_TEXT || form->opts->type == OC_FORM_OPT_PASSWORD || form->opts->type == OC_FORM_OPT_HIDDEN || - form->opts->type == OC_FORM_OPT_STOKEN) + form->opts->type == OC_FORM_OPT_TOKEN) free(form->opts->value); else if (form->opts->type == OC_FORM_OPT_SELECT) { struct oc_form_opt_select *sel = (void *)form->opts; @@ -872,8 +879,8 @@ int prepare_stoken(struct openconnect_info *vpninfo) form.opts = opts; form.message = _("Enter credentials to unlock software token."); - vpninfo->stoken_tries = 0; - vpninfo->stoken_bypassed = 0; + vpninfo->token_tries = 0; + vpninfo->token_bypassed = 0; if (stoken_devid_required(vpninfo->stoken_ctx)) { opt->type = OC_FORM_OPT_TEXT; @@ -923,7 +930,7 @@ int prepare_stoken(struct openconnect_info *vpninfo) if (all_empty) { vpn_progress(vpninfo, PRG_INFO, _("User bypassed soft token.\n")); - vpninfo->stoken_bypassed = 1; + vpninfo->token_bypassed = 1; ret = 0; break; } @@ -981,22 +988,23 @@ int prepare_stoken(struct openconnect_info *vpninfo) * < 0, if unable to generate a tokencode * = 0, on success */ -static int can_gen_tokencode(struct openconnect_info *vpninfo, struct oc_auth_form *form, - struct oc_form_opt *opt) +static int can_gen_stoken_code(struct openconnect_info *vpninfo, + struct oc_auth_form *form, + struct oc_form_opt *opt) { #ifdef LIBSTOKEN_HDR if ((strcmp(opt->name, "password") && strcmp(opt->name, "answer")) || - vpninfo->stoken_bypassed) + vpninfo->token_bypassed) return -EINVAL; - if (vpninfo->stoken_tries == 0) { + if (vpninfo->token_tries == 0) { vpn_progress(vpninfo, PRG_DEBUG, _("OK to generate INITIAL tokencode\n")); - vpninfo->stoken_time = 0; - } else if (vpninfo->stoken_tries == 1 && form->message && + vpninfo->token_time = 0; + } else if (vpninfo->token_tries == 1 && form->message && strcasestr(form->message, "next tokencode")) { vpn_progress(vpninfo, PRG_DEBUG, _("OK to generate NEXT tokencode\n")); - vpninfo->stoken_time += 60; + vpninfo->token_time += 60; } else { /* limit the number of retries, to avoid account lockouts */ vpn_progress(vpninfo, PRG_INFO, @@ -1013,35 +1021,139 @@ static int can_gen_tokencode(struct openconnect_info *vpninfo, struct oc_auth_fo * < 0, if unable to generate a tokencode * = 0, on success */ -static int do_gen_tokencode(struct openconnect_info *vpninfo, struct oc_auth_form *form) +static int can_gen_totp_code(struct openconnect_info *vpninfo, + struct oc_auth_form *form, + struct oc_form_opt *opt) { -#ifdef LIBSTOKEN_HDR - char tokencode[STOKEN_MAX_TOKENCODE + 1]; - struct oc_form_opt *opt; +#if defined(LIBOATH_HDR) + if ((strcmp(opt->name, "secondary_password") != 0) || + vpninfo->token_bypassed) + return -EINVAL; + if (vpninfo->token_tries == 0) { + vpn_progress(vpninfo, PRG_DEBUG, + _("OK to generate INITIAL tokencode\n")); + vpninfo->token_time = 0; + } else if (vpninfo->token_tries == 1) { + vpn_progress(vpninfo, PRG_DEBUG, + _("OK to generate NEXT tokencode\n")); + vpninfo->token_time += OATH_TOTP_DEFAULT_TIME_STEP_SIZE; + } else { + /* limit the number of retries, to avoid account lockouts */ + vpn_progress(vpninfo, PRG_INFO, + _("Server is rejecting the soft token; switching to manual entry\n")); + return -ENOENT; + } + return 0; +#else + return -EOPNOTSUPP; +#endif +} - for (opt = form->opts; ; opt = opt->next) { - /* this form might not have anything for us to do */ - if (!opt) - return 0; - if (opt->type == OC_FORM_OPT_STOKEN) - break; +/* Return value: + * < 0, if unable to generate a tokencode + * = 0, on success + */ +static int can_gen_tokencode(struct openconnect_info *vpninfo, + struct oc_auth_form *form, + struct oc_form_opt *opt) +{ + switch (vpninfo->token_mode) { + case TOKEN_MODE_STOKEN: + return can_gen_stoken_code(vpninfo, form, opt); + + case TOKEN_MODE_TOTP: + return can_gen_totp_code(vpninfo, form, opt); + + default: + return -EINVAL; } +} - if (!vpninfo->stoken_time) - vpninfo->stoken_time = time(NULL); - vpn_progress(vpninfo, PRG_INFO, _("Generating tokencode\n")); +static int do_gen_stoken_code(struct openconnect_info *vpninfo, + struct oc_auth_form *form, + struct oc_form_opt *opt) +{ +#ifdef LIBSTOKEN_HDR + char tokencode[STOKEN_MAX_TOKENCODE + 1]; + + if (!vpninfo->token_time) + vpninfo->token_time = time(NULL); + vpn_progress(vpninfo, PRG_INFO, _("Generating RSA token code\n")); /* This doesn't normally fail */ - if (stoken_compute_tokencode(vpninfo->stoken_ctx, vpninfo->stoken_time, + if (stoken_compute_tokencode(vpninfo->stoken_ctx, vpninfo->token_time, vpninfo->stoken_pin, tokencode) < 0) { vpn_progress(vpninfo, PRG_ERR, _("General failure in libstoken.\n")); return -EIO; } - vpninfo->stoken_tries++; + vpninfo->token_tries++; opt->value = strdup(tokencode); return opt->value ? 0 : -ENOMEM; #else return 0; #endif } + +static int do_gen_totp_code(struct openconnect_info *vpninfo, + struct oc_auth_form *form, + struct oc_form_opt *opt) +{ +#if defined(LIBOATH_HDR) + int oath_err; + char tokencode[7]; + + if (!vpninfo->token_time) { + vpninfo->token_time = time(NULL); + } + vpn_progress(vpninfo, PRG_INFO, _("Generating OATH TOTP token code\n")); + + oath_err = oath_totp_generate(vpninfo->oath_secret, + vpninfo->oath_secret_len, + vpninfo->token_time, + OATH_TOTP_DEFAULT_TIME_STEP_SIZE, + OATH_TOTP_DEFAULT_START_TIME, + 6, tokencode); + if (oath_err != OATH_OK) { + vpn_progress(vpninfo, PRG_ERR, + _("Unable to generate OATH TOTP token code: %s\n"), + oath_strerror(oath_err)); + return -EIO; + } + + vpninfo->token_tries++; + opt->value = strdup(tokencode); + return opt->value ? 0 : -ENOMEM; +#else + return 0; +#endif +} + +/* Return value: + * < 0, if unable to generate a tokencode + * = 0, on success + */ +static int do_gen_tokencode(struct openconnect_info *vpninfo, + struct oc_auth_form *form) +{ + struct oc_form_opt *opt; + + for (opt = form->opts; ; opt = opt->next) { + /* this form might not have anything for us to do */ + if (!opt) + return 0; + if (opt->type == OC_FORM_OPT_TOKEN) + break; + } + + switch (vpninfo->token_mode) { + case TOKEN_MODE_STOKEN: + return do_gen_stoken_code(vpninfo, form, opt); + + case TOKEN_MODE_TOTP: + return do_gen_totp_code(vpninfo, form, opt); + + default: + return -EINVAL; + } +} diff --git a/configure.ac b/configure.ac index 89887f5..15b3b51 100644 --- a/configure.ac +++ b/configure.ac @@ -490,6 +490,12 @@ PKG_CHECK_MODULES(LIBSTOKEN, stoken, libstoken_pkg=yes], libstoken_pkg=no) +PKG_CHECK_MODULES(LIBOATH, liboath, + [AC_SUBST(LIBOATH_PC, liboath) + AC_DEFINE([LIBOATH_HDR], ["liboath/oath.h"]) + liboath_pkg=yes], + liboath_pkg=no) + AC_CHECK_HEADER([if_tun.h], [AC_DEFINE([IF_TUN_HDR], ["if_tun.h"])], [AC_CHECK_HEADER([linux/if_tun.h], diff --git a/http.c b/http.c index 9bdc9d9..a7bc522 100644 --- a/http.c +++ b/http.c @@ -938,7 +938,7 @@ int openconnect_obtain_cookie(struct openconnect_info *vpninfo) int xmlpost = 0; /* Step 1: Unlock software token (if applicable) */ - if (vpninfo->use_stoken) { + if (vpninfo->token_mode == TOKEN_MODE_STOKEN) { result = prepare_stoken(vpninfo); if (result) return result; diff --git a/libopenconnect.map.in b/libopenconnect.map.in index eae3e70..f38c380 100644 --- a/libopenconnect.map.in +++ b/libopenconnect.map.in @@ -33,7 +33,9 @@ OPENCONNECT_2.0 { OPENCONNECT_2.1 { global: openconnect_has_stoken_support; + openconnect_has_oath_support; openconnect_set_stoken_mode; + openconnect_set_oath_mode; openconnect_set_reported_os; } OPENCONNECT_2.0; diff --git a/library.c b/library.c index be4b119..32d9331 100644 --- a/library.c +++ b/library.c @@ -1,6 +1,7 @@ /* * OpenConnect (SSL + DTLS) VPN client * + * Copyright ? 2013 John Morrissey <jwm at horde.net> * Copyright ? 2008-2012 Intel Corporation. * * Authors: David Woodhouse <dwmw2 at infradead.org> @@ -30,6 +31,10 @@ #include LIBSTOKEN_HDR #endif +#ifdef LIBOATH_HDR +#include LIBOATH_HDR +#endif + #include <libxml/tree.h> #include "openconnect-internal.h" @@ -145,6 +150,10 @@ void openconnect_vpninfo_free (struct openconnect_info *vpninfo) if (vpninfo->stoken_ctx) stoken_destroy(vpninfo->stoken_ctx); #endif +#ifdef LIBOATH_HDR + if (vpninfo->oath_secret) + oath_done(); +#endif /* No need to free deflate streams; they weren't initialised */ free(vpninfo); } @@ -321,8 +330,17 @@ int openconnect_has_stoken_support(void) #endif } +int openconnect_has_oath_support(void) +{ +#ifdef LIBOATH_HDR + return 1; +#else + return 0; +#endif +} + /* - * Enable software token generation if use_stoken == 1. + * Enable libstoken token generation. * * If token_str is not NULL, try to parse the string. Otherwise, try to read * the token data from ~/.stokenrc @@ -335,15 +353,11 @@ int openconnect_has_stoken_support(void) * = 0, on success */ int openconnect_set_stoken_mode (struct openconnect_info *vpninfo, - int use_stoken, const char *token_str) + const char *token_str) { #ifdef LIBSTOKEN_HDR int ret; - vpninfo->use_stoken = 0; - if (!use_stoken) - return 0; - if (!vpninfo->stoken_ctx) { vpninfo->stoken_ctx = stoken_new(); if (!vpninfo->stoken_ctx) @@ -356,7 +370,45 @@ int openconnect_set_stoken_mode (struct openconnect_info *vpninfo, if (ret) return ret; - vpninfo->use_stoken = 1; + return 0; +#else + return -EOPNOTSUPP; +#endif +} + +/* + * Enable OATH software token generation. + * + * Return value: + * = -EOPNOTSUPP, if libstoken is not available + * = -EINVAL, if the token string is invalid (token_str was provided) + * = -EIO, for other libstoken failures + * = 0, on success + */ +int openconnect_set_oath_mode (struct openconnect_info *vpninfo, + const char *token_str) +{ +#ifdef LIBOATH_HDR + int ret; + + ret = oath_init(); + if (ret != OATH_OK) { + return -EIO; + } + + if (strncasecmp(token_str, "base32:", strlen("base32:")) == 0) { + ret = oath_base32_decode(token_str + strlen("base32:"), + strlen(token_str) - strlen("base32:"), + &vpninfo->oath_secret, + &vpninfo->oath_secret_len); + if (ret != OATH_OK) { + return -EINVAL; + } + } else { + vpninfo->oath_secret = strdup(token_str); + vpninfo->oath_secret_len = strlen(token_str); + } + return 0; #else return -EOPNOTSUPP; diff --git a/main.c b/main.c index d40207b..00197c1 100644 --- a/main.c +++ b/main.c @@ -1,6 +1,7 @@ /* * OpenConnect (SSL + DTLS) VPN client * + * Copyright ? 2013 John Morrissey <jwm at horde.net> * Copyright ? 2008-2012 Intel Corporation. * Copyright ? 2008 Nick Andrew <nick at nick-andrew.net> * @@ -66,7 +67,7 @@ static int validate_peer_cert(void *_vpninfo, const char *reason); static int process_auth_form(void *_vpninfo, struct oc_auth_form *form); -static void init_stoken(struct openconnect_info *vpninfo, +static void init_token(struct openconnect_info *vpninfo, const char *token_str); /* A sanity check that the openconnect executable is running against a @@ -110,7 +111,8 @@ enum { OPT_USERAGENT, OPT_NON_INTER, OPT_DTLS_LOCAL_PORT, - OPT_STOKEN, + OPT_TOKEN_MODE, + OPT_TOKEN_SECRET, OPT_OS, }; @@ -175,7 +177,10 @@ static struct option long_options[] = { OPTION("force-dpd", 1, OPT_FORCE_DPD), OPTION("non-inter", 0, OPT_NON_INTER), OPTION("dtls-local-port", 1, OPT_DTLS_LOCAL_PORT), - OPTION("stoken", 2, OPT_STOKEN), + OPTION("token-mode", 1, OPT_TOKEN_MODE), + /* Alias --stoken to --token-secret for backwards compatibility. */ + OPTION("stoken", 2, OPT_TOKEN_SECRET), + OPTION("token-secret", 2, OPT_TOKEN_SECRET), OPTION("os", 1, OPT_OS), OPTION(NULL, 0, 0) }; @@ -281,10 +286,15 @@ static void usage(void) printf(" --no-cert-check %s\n", _("Do not require server SSL cert to be valid")); printf(" --non-inter %s\n", _("Do not expect user input; exit if it is required")); printf(" --passwd-on-stdin %s\n", _("Read password from standard input")); - printf(" --stoken[=TOKENSTRING] %s\n", _("Use software token to generate password")); + printf(" --token-mode=MODE %s\n", _("Software token type: stoken (default) or totp")); + printf(" --token-secret[=STRING] %s\n", _("Software token secret (can be empty for stoken mode")); + printf(" %s\n", _(" to read from ~/.stokenrc)")); #ifndef LIBSTOKEN_HDR printf(" %s\n", _("(NOTE: libstoken disabled in this build)")); #endif +#ifndef LIBOATH_HDR + printf(" %s\n", _("(NOTE: liboath (TOTP) disabled in this build)")); +#endif printf(" --reconnect-timeout %s\n", _("Connection retry timeout in seconds")); printf(" --servercert=FINGERPRINT %s\n", _("Server's certificate SHA1 fingerprint")); printf(" --useragent=STRING %s\n", _("HTTP header User-Agent: field")); @@ -448,7 +458,6 @@ int main(int argc, char **argv) char *pidfile = NULL; FILE *fp = NULL; char *config_arg; - int use_stoken = 0; char *token_str = NULL; #ifdef ENABLE_NLS @@ -491,6 +500,7 @@ int main(int argc, char **argv) vpninfo->cert_expire_warning = 60 * 86400; vpninfo->vpnc_script = DEFAULT_VPNCSCRIPT; vpninfo->cancel_fd = -1; + vpninfo->token_mode = TOKEN_MODE_NONE; if (!uname(&utsbuf)) vpninfo->localname = utsbuf.nodename; @@ -712,8 +722,18 @@ int main(int argc, char **argv) case OPT_DTLS_LOCAL_PORT: vpninfo->dtls_local_port = atoi(config_arg); break; - case OPT_STOKEN: - use_stoken = 1; + case OPT_TOKEN_MODE: + if (strcasecmp(config_arg, "stoken") == 0) { + vpninfo->token_mode = TOKEN_MODE_STOKEN; + } else if (strcasecmp(config_arg, "totp") == 0) { + vpninfo->token_mode = TOKEN_MODE_TOTP; + } else { + fprintf(stderr, _("Invalid software token mode \"%s\"\n"), + config_arg); + exit(1); + } + break; + case OPT_TOKEN_SECRET: token_str = keep_config_arg(); break; case OPT_OS: @@ -750,8 +770,8 @@ int main(int argc, char **argv) #endif } - if (use_stoken) - init_stoken(vpninfo, token_str); + if (vpninfo->token_mode != TOKEN_MODE_NONE) + init_token(vpninfo, token_str); if (proxy && openconnect_set_http_proxy(vpninfo, strdup(proxy))) exit(1); @@ -1225,25 +1245,52 @@ static int process_auth_form(void *_vpninfo, return -EINVAL; } -static void init_stoken(struct openconnect_info *vpninfo, - const char *token_str) +static void init_token(struct openconnect_info *vpninfo, + const char *token_str) { - int ret = openconnect_set_stoken_mode(vpninfo, 1, token_str); + int ret; - switch (ret) { - case 0: - return; - case -EINVAL: - fprintf(stderr, _("Soft token string is invalid\n")); - exit(1); - case -ENOENT: - fprintf(stderr, _("Can't open ~/.stokenrc file\n")); - exit(1); - case -EOPNOTSUPP: - fprintf(stderr, _("OpenConnect was not built with soft token support\n")); - exit(1); - default: - fprintf(stderr, _("General failure in libstoken\n")); - exit(1); + switch (vpninfo->token_mode) { + case TOKEN_MODE_STOKEN: + ret = openconnect_set_stoken_mode(vpninfo, token_str); + + switch (ret) { + case 0: + return; + case -EINVAL: + fprintf(stderr, _("Soft token string is invalid\n")); + exit(1); + case -ENOENT: + fprintf(stderr, _("Can't open ~/.stokenrc file\n")); + exit(1); + case -EOPNOTSUPP: + fprintf(stderr, _("OpenConnect was not built with soft token support\n")); + exit(1); + default: + fprintf(stderr, _("General failure in liboath\n")); + exit(1); + } + + break; + case TOKEN_MODE_TOTP: + ret = openconnect_set_oath_mode(vpninfo, token_str); + + switch (ret) { + case 0: + return; + case -EINVAL: + fprintf(stderr, _("Soft token string is invalid\n")); + exit(1); + case -EOPNOTSUPP: + fprintf(stderr, _("OpenConnect was not built with liboath support\n")); + exit(1); + default: + fprintf(stderr, _("General failure in libstoken\n")); + exit(1); + } + + break; + + /* Option parsing already checked for invalid modes. */ } } diff --git a/openconnect-internal.h b/openconnect-internal.h index ab3926e..4bc70d1 100644 --- a/openconnect-internal.h +++ b/openconnect-internal.h @@ -1,6 +1,7 @@ /* * OpenConnect (SSL + DTLS) VPN client * + * Copyright ? 2013 John Morrissey <jwm at horde.net> * Copyright ? 2008-2012 Intel Corporation. * Copyright ? 2008 Nick Andrew <nick at nick-andrew.net> * @@ -65,6 +66,12 @@ #include LIBSTOKEN_HDR #endif +enum { + TOKEN_MODE_NONE, + TOKEN_MODE_STOKEN, + TOKEN_MODE_TOTP, +}; + #ifdef ENABLE_NLS #include <locale.h> #include <libintl.h> @@ -180,14 +187,18 @@ struct openconnect_info { int uid_csd_given; int no_http_keepalive; + int token_mode; + int token_bypassed; + int token_tries; + time_t token_time; #ifdef LIBSTOKEN_HDR struct stoken_ctx *stoken_ctx; -#endif - int use_stoken; - int stoken_bypassed; - int stoken_tries; - time_t stoken_time; char *stoken_pin; +#endif +#ifdef LIBOATH_HDR + char *oath_secret; + size_t oath_secret_len; +#endif OPENCONNECT_X509 *peer_cert; diff --git a/openconnect.8.in b/openconnect.8.in index b941144..5703099 100644 --- a/openconnect.8.in +++ b/openconnect.8.in @@ -49,7 +49,8 @@ openconnect \- Connect to Cisco AnyConnect VPN .OP \-\-no\-passwd .OP \-\-non\-inter .OP \-\-passwd\-on\-stdin -.OP \-\-stoken[=\fItoken-string\fP] +.OP \-\-token-mode=\fIstoken|totp\fP +.OP \-\-token-secret=\fIsecret\fP .OP \-\-reconnect\-timeout .OP \-\-servercert sha1 .OP \-\-useragent string @@ -324,11 +325,17 @@ Do not expect user input; exit if it is required. .B \-\-passwd\-on\-stdin Read password from standard input .TP -.B \-\-stoken[=\fItoken-string\fP] -Use libstoken to generate one-time passwords compatible with the RSA SecurID -system (when built with libstoken support). If \fItoken-string\fP is omitted, -libstoken will try to use the software token seed stored in \fI~/.stokenrc\fP, -if this file exists. +.B \-\-token\-mode=\fIstoken|totp\fP +Select the algorithm to use to generate one-time passwords/verification +codes. \fIstoken\fP for RSA SecureID requires libstoken, and \fItotp\fP +for RFC 6238 requires liboath. +.TP +.B \-\-token\-secret[=\fIsecret\fP] +The secret to use when generating one-time passwords/verification codes. +If \fIsecret\fP is omitted and \-\-token-mode is \fIstoken\fP, libstoken +will try to use the software token seed stored in \fI~/.stokenrc\fP, if this +file exists. Base 32-encoded TOTP secrets can be specified by specifying +"base32:" at the beginning of the secret. .TP .B \-\-reconnect\-timeout Keep reconnect attempts until so much seconds are elapsed. The default diff --git a/openconnect.h b/openconnect.h index 95cb6e8..174cf10 100644 --- a/openconnect.h +++ b/openconnect.h @@ -1,6 +1,7 @@ /* * OpenConnect (SSL + DTLS) VPN client * + * Copyright ? 2013 John Morrissey <jwm at horde.net> * Copyright ? 2008-2012 Intel Corporation. * Copyright ? 2008 Nick Andrew <nick at nick-andrew.net> * @@ -86,7 +87,7 @@ #define OC_FORM_OPT_PASSWORD 2 #define OC_FORM_OPT_SELECT 3 #define OC_FORM_OPT_HIDDEN 4 -#define OC_FORM_OPT_STOKEN 5 +#define OC_FORM_OPT_TOKEN 5 /* char * fields are static (owned by XML parser) and don't need to be freed by the form handling code -- except for value, which for TEXT @@ -163,10 +164,12 @@ void openconnect_set_hostname (struct openconnect_info *, char *); char *openconnect_get_urlpath (struct openconnect_info *); void openconnect_set_urlpath (struct openconnect_info *, char *); -/* This function does *not* take ownership of the string; it is parsed +/* These functions do *not* take ownership of the string; it is parsed and then discarded. */ int openconnect_set_stoken_mode (struct openconnect_info *, - int use_stoken, const char *token_str); + const char *token_str); +int openconnect_set_oath_mode (struct openconnect_info *, + const char *token_str); /* This function does *not* take ownership of the string; it's copied into a static buffer in the vpninfo. The size must be 41 bytes, @@ -262,5 +265,6 @@ int openconnect_has_tss_blob_support(void); /* Software token capabilities. */ int openconnect_has_stoken_support(void); +int openconnect_has_oath_support(void); #endif /* __OPENCONNECT_H__ */ diff --git a/www/building.xml b/www/building.xml index 07f3689..ac06694 100644 --- a/www/building.xml +++ b/www/building.xml @@ -33,6 +33,7 @@ And <em>optionally</em> also: <li><b><tt><a href="http://code.google.com/p/libproxy/">libproxy</a></tt></b></li> <li><b><tt><a href="http://trousers.sourceforge.net/">trousers</a></tt></b> <i>(for TPM support if using GnuTLS)</i></li> <li><b><tt><a href="http://stoken.sourceforge.net/">libstoken</a></tt></b> <i>(for SecurID software token support)</i></li> + <li><b><tt><a href="http://www.nongnu.org/oath-toolkit/">liboath</a></tt></b> <i>(for RFC6238 TOTP support)</i></li> </ul> <p>OpenConnect supports the use of HTTP and SOCKS proxies to connect to the AnyConnect service, even without using libproxy. You may wish to use libproxy diff --git a/www/changelog.xml b/www/changelog.xml index be07c95..13eb07f 100644 --- a/www/changelog.xml +++ b/www/changelog.xml @@ -22,6 +22,7 @@ <li>Fix compatibility issues with XML POST authentication.</li> <li>Fix memory leaks on <tt>realloc()</tt> failure.</li> <li>Fix certificate validation problem caused by hostname canonicalisation.</li> + <li>Add RFC6238 TOTP token support using <a href="http://www.nongnu.org/oath-toolkit/">liboath</a>.</li> </ul><br/> </li> <li><b><a href="ftp://ftp.infradead.org/pub/openconnect/openconnect-4.99.tar.gz">OpenConnect v4.99</a></b> diff --git a/www/features.xml b/www/features.xml index 0f8eeec..4060c4a 100644 --- a/www/features.xml +++ b/www/features.xml @@ -18,6 +18,7 @@ <li>Authentication via HTTP forms.</li> <li>Authentication using SSL certificates — from local file, <a href="http://en.wikipedia.org/wiki/Trusted_Platform_Module">Trusted Platform Module</a> and <i>(when built with GnuTLS)</i> PKCS#11 smartcards.</li> <li>Authentication using SecurID software tokens <i>(when built with libstoken)</i></li> + <li>Authentication using RFC6238 TOTP software tokens <i>(when built with liboath)</i></li> <li><i>UserGroup</i> support for selecting between multiple configurations on a single VPN server.</li> <li>Data transport over TCP <i>(HTTPS)</i> or UDP <i>(DTLS)</i>.</li> <li>Keepalive and Dead Peer Detection on both HTTPS and DTLS.</li> -- 1.7.2.5 -- John Morrissey _o /\ ---- __o jwm at horde.net _-< \_ / \ ---- < \, www.horde.net/ __(_)/_(_)________/ \_______(_) /_(_)__