Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add TOTP (RFC6238) one-time password support
Signed-off-by: John Morrissey <jwm@horde.net>
Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
  • Loading branch information
jwm authored and David Woodhouse committed Mar 23, 2013
1 parent cbdb050 commit 4bd4222
Show file tree
Hide file tree
Showing 13 changed files with 387 additions and 87 deletions.
8 changes: 4 additions & 4 deletions Makefile.am
Expand Up @@ -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
Expand All @@ -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
Expand Down
172 changes: 142 additions & 30 deletions auth.c
Expand Up @@ -3,6 +3,7 @@
*
* Copyright © 2008-2011 Intel Corporation.
* Copyright © 2008 Nick Andrew <nick@nick-andrew.net>
* Copyright © 2013 John Morrissey <jwm@horde.net>
*
* Author: David Woodhouse <dwmw2@infradead.org>
*
Expand Down Expand Up @@ -36,6 +37,10 @@
#include LIBSTOKEN_HDR
#endif

#ifdef LIBOATH_HDR
#include LIBOATH_HDR
#endif

#include <libxml/parser.h>
#include <libxml/tree.h>

Expand Down Expand Up @@ -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 != OC_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"),
Expand Down Expand Up @@ -637,7 +644,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;
Expand Down Expand Up @@ -878,8 +885,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;
Expand Down Expand Up @@ -929,7 +936,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;
}
Expand Down Expand Up @@ -987,22 +994,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,
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,
Expand All @@ -1019,35 +1027,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;

for (opt = form->opts; ; opt = opt->next) {
/* this form might not have anything for us to do */
if (!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;
if (opt->type == OC_FORM_OPT_STOKEN)
break;
#else
return -EOPNOTSUPP;
#endif
}

/* 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 OC_TOKEN_MODE_STOKEN:
return can_gen_stoken_code(vpninfo, form, opt);

case OC_TOKEN_MODE_TOTP:
return can_gen_totp_code(vpninfo, form, opt);

default:
return -EINVAL;
}
}

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->stoken_time)
vpninfo->stoken_time = time(NULL);
vpn_progress(vpninfo, PRG_INFO, _("Generating tokencode\n"));
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 OC_TOKEN_MODE_STOKEN:
return do_gen_stoken_code(vpninfo, form, opt);

case OC_TOKEN_MODE_TOTP:
return do_gen_totp_code(vpninfo, form, opt);

default:
return -EINVAL;
}
}
11 changes: 11 additions & 0 deletions configure.ac
Expand Up @@ -504,6 +504,17 @@ AS_IF([test "x$with_stoken" != "xno"], [
libstoken_pkg=no)
], [libstoken_pkg=disabled])

AC_ARG_WITH([liboath],
AS_HELP_STRING([--without-liboath],
[Build without liboath library (default: test)]))
AS_IF([test "x$with_liboath" != "xno"], [
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],
Expand Down
2 changes: 1 addition & 1 deletion http.c
Expand Up @@ -948,7 +948,7 @@ int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
int xmlpost = 1;

/* Step 1: Unlock software token (if applicable) */
if (vpninfo->use_stoken) {
if (vpninfo->token_mode == OC_TOKEN_MODE_STOKEN) {
result = prepare_stoken(vpninfo);
if (result)
return result;
Expand Down
6 changes: 6 additions & 0 deletions libopenconnect.map.in
Expand Up @@ -37,6 +37,12 @@ OPENCONNECT_2.1 {
openconnect_set_reported_os;
} OPENCONNECT_2.0;

OPENCONNECT_2.2 {
global:
openconnect_has_oath_support;
openconnect_set_token_mode;
} OPENCONNECT_2.1;

OPENCONNECT_PRIVATE {
global: @SYMVER_TIME@ @SYMVER_ASPRINTF@ @SYMVER_GETLINE@ @SYMVER_PRINT_ERR@
openconnect_SSL_gets;
Expand Down

0 comments on commit 4bd4222

Please sign in to comment.