Skip to content

Commit

Permalink
Add RFC4226 HOTP token support
Browse files Browse the repository at this point in the history
This isn't really complete since it doesn't handle the token counter
properly. It relies on being given the token counter along with the
secret key, and there's no way to save the new value when we're done.

We could perhaps add a library function to write the token counter back,
and rely on the library user to manage the file storage containing the
counter.

Or maybe we want to use libpskc and allow the PSKC file to be specified,
then we can update that file directly.

A UI tool might also want to store the PSKC data in something like the
keyring instead of a simple file, so in that case the library should
probably allow for a callback which provides the new PSKC data rather
than unconditionally writing it to a file.

Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
  • Loading branch information
David Woodhouse authored and David Woodhouse committed Feb 18, 2014
1 parent c02460c commit ef31b98
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 13 deletions.
67 changes: 67 additions & 0 deletions auth.c
Expand Up @@ -1169,6 +1169,37 @@ static int can_gen_totp_code(struct openconnect_info *vpninfo,
#endif
}

/* Return value:
* < 0, if unable to generate a tokencode
* = 0, on success
*/
static int can_gen_hotp_code(struct openconnect_info *vpninfo,
struct oc_auth_form *form,
struct oc_form_opt *opt)
{
#ifdef HAVE_LIBOATH
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"));
} else if (vpninfo->token_tries == 1) {
vpn_progress(vpninfo, PRG_DEBUG,
_("OK to generate NEXT tokencode\n"));
vpninfo->token_time++;
} 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
}

/* Return value:
* < 0, if unable to generate a tokencode
* = 0, on success
Expand All @@ -1184,6 +1215,9 @@ static int can_gen_tokencode(struct openconnect_info *vpninfo,
case OC_TOKEN_MODE_TOTP:
return can_gen_totp_code(vpninfo, form, opt);

case OC_TOKEN_MODE_HOTP:
return can_gen_hotp_code(vpninfo, form, opt);

default:
return -EINVAL;
}
Expand Down Expand Up @@ -1249,6 +1283,36 @@ static int do_gen_totp_code(struct openconnect_info *vpninfo,
#endif
}

static int do_gen_hotp_code(struct openconnect_info *vpninfo,
struct oc_auth_form *form,
struct oc_form_opt *opt)
{
#ifdef HAVE_LIBOATH
int oath_err;
char tokencode[7];

vpn_progress(vpninfo, PRG_INFO, _("Generating OATH HOTP token code\n"));

oath_err = oath_hotp_generate(vpninfo->oath_secret,
vpninfo->oath_secret_len,
vpninfo->token_time,
6, false, OATH_HOTP_DYNAMIC_TRUNCATION,
tokencode);
if (oath_err != OATH_OK) {
vpn_progress(vpninfo, PRG_ERR,
_("Unable to generate OATH HOTP 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
Expand All @@ -1273,6 +1337,9 @@ static int do_gen_tokencode(struct openconnect_info *vpninfo,
case OC_TOKEN_MODE_TOTP:
return do_gen_totp_code(vpninfo, form, opt);

case OC_TOKEN_MODE_HOTP:
return do_gen_hotp_code(vpninfo, form, opt);

default:
return -EINVAL;
}
Expand Down
51 changes: 48 additions & 3 deletions library.c
Expand Up @@ -445,7 +445,7 @@ int openconnect_has_stoken_support(void)
int openconnect_has_oath_support(void)
{
#ifdef HAVE_LIBOATH
return 1;
return 2;
#else
return 0;
#endif
Expand Down Expand Up @@ -476,7 +476,7 @@ static int set_libstoken_mode(struct openconnect_info *vpninfo,
#endif
}

static int set_oath_mode(struct openconnect_info *vpninfo,
static int set_totp_mode(struct openconnect_info *vpninfo,
const char *token_str)
{
#ifdef HAVE_LIBOATH
Expand Down Expand Up @@ -505,6 +505,48 @@ static int set_oath_mode(struct openconnect_info *vpninfo,
#endif
}

static int set_hotp_mode(struct openconnect_info *vpninfo,
const char *token_str)
{
#ifdef HAVE_LIBOATH
int ret, toklen;
char *p;

ret = oath_init();
if (ret != OATH_OK)
return -EIO;

toklen = strlen(token_str);
p = strrchr(token_str, ',');
if (p) {
long counter;
toklen = p - token_str;
p++;
counter = strtol(p, &p, 0);
if (counter < 0 || *p)
return -EINVAL;
vpninfo->token_time = counter;
}

if (strncasecmp(token_str, "base32:", strlen("base32:")) == 0) {
ret = oath_base32_decode(token_str + strlen("base32:"),
toklen - 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 = toklen;
}

vpninfo->token_mode = OC_TOKEN_MODE_HOTP;
return 0;
#else
return -EOPNOTSUPP;
#endif
}

/*
* Enable software token generation.
*
Expand Down Expand Up @@ -534,7 +576,10 @@ int openconnect_set_token_mode(struct openconnect_info *vpninfo,
return set_libstoken_mode(vpninfo, token_str);

case OC_TOKEN_MODE_TOTP:
return set_oath_mode(vpninfo, token_str);
return set_totp_mode(vpninfo, token_str);

case OC_TOKEN_MODE_HOTP:
return set_hotp_mode(vpninfo, token_str);

default:
return -EOPNOTSUPP;
Expand Down
13 changes: 10 additions & 3 deletions main.c
Expand Up @@ -272,7 +272,11 @@ static void print_build_opts(void)
printf("%sRSA software token", sep);
sep = comma;
}
if (openconnect_has_oath_support()) {
switch(openconnect_has_oath_support()) {
case 2:
printf("%sHOTP software token", sep);
sep = comma;
case 1:
printf("%sTOTP software token", sep);
sep = comma;
}
Expand Down Expand Up @@ -380,13 +384,13 @@ static void usage(void)
printf(" --no-xmlpost %s\n", _("Do not attempt XML POST authentication"));
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(" --token-mode=MODE %s\n", _("Software token type: rsa or totp"));
printf(" --token-mode=MODE %s\n", _("Software token type: rsa, totp or hotp"));
printf(" --token-secret=STRING %s\n", _("Software token secret"));
#ifndef HAVE_LIBSTOKEN
printf(" %s\n", _("(NOTE: libstoken (RSA SecurID) disabled in this build)"));
#endif
#ifndef HAVE_LIBOATH
printf(" %s\n", _("(NOTE: liboath (TOTP) disabled in this build)"));
printf(" %s\n", _("(NOTE: liboath (TOTP,HOTP) 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"));
Expand Down Expand Up @@ -882,6 +886,8 @@ int main(int argc, char **argv)
token_mode = OC_TOKEN_MODE_STOKEN;
} else if (strcasecmp(config_arg, "totp") == 0) {
token_mode = OC_TOKEN_MODE_TOTP;
} else if (strcasecmp(config_arg, "hotp") == 0) {
token_mode = OC_TOKEN_MODE_HOTP;
} else {
fprintf(stderr, _("Invalid software token mode \"%s\"\n"),
config_arg);
Expand Down Expand Up @@ -1467,6 +1473,7 @@ static void init_token(struct openconnect_info *vpninfo,
break;

case OC_TOKEN_MODE_TOTP:
case OC_TOKEN_MODE_HOTP:
switch (ret) {
case 0:
return;
Expand Down
17 changes: 11 additions & 6 deletions openconnect.8.in
Expand Up @@ -54,7 +54,7 @@ openconnect \- Connect to Cisco AnyConnect VPN
.OP \-\-non\-inter
.OP \-\-passwd\-on\-stdin
.OP \-\-token-mode mode
.OP \-\-token-secret secret
.OP \-\-token-secret secret\fR[\fI,counter\fR]
.OP \-\-reconnect\-timeout
.OP \-\-servercert sha1
.OP \-\-useragent string
Expand Down Expand Up @@ -370,14 +370,19 @@ Enable one-time password generation using the
.I MODE
algorithm.
.B \-\-token\-mode=rsa
will call libstoken to generate an RSA SecurID tokencode, and
will call libstoken to generate an RSA SecurID tokencode,
.B \-\-token\-mode=totp
will call liboath to generate an RFC 6238 password.
will call liboath to generate an RFC 6238 time-based password, and
.B \-\-token\-mode=hotp
will call liboath to generate an RFC 4226 HMAC-based password.
.TP
.B \-\-token\-secret=SECRET
.B \-\-token\-secret=SECRET[,COUNTER]
The secret to use when generating one-time passwords/verification codes.
Base 32-encoded TOTP secrets can be used by specifying "base32:" at the
beginning of the secret. If this option is omitted, and \-\-token\-mode is
Base 32-encoded TOTP/HOTP secrets can be used by specifying "base32:" at the
beginning of the secret, and for HOTP secrets the token counter can be
specified following a comma.

If this option is omitted, and \-\-token\-mode is
"rsa", libstoken will try to use the software token seed saved in
.B ~/.stokenrc
by the "stoken import" command.
Expand Down
7 changes: 6 additions & 1 deletion openconnect.h
Expand Up @@ -29,9 +29,13 @@
#endif

#define OPENCONNECT_API_VERSION_MAJOR 3
#define OPENCONNECT_API_VERSION_MINOR 1
#define OPENCONNECT_API_VERSION_MINOR 2

/*
* API version 3.2:
* - Add OC_TOKEN_MODE_HOTP and allow openconnect_has_oath_support() to
* return 2 to indicate that it is present.
*
* API version 3.1:
* - Add openconnect_setup_cmd_pipe(), openconnect_mainloop(),
* openconnect_setup_tun_device(), openconnect_setup_tun_script(),
Expand Down Expand Up @@ -224,6 +228,7 @@ typedef enum {
OC_TOKEN_MODE_NONE,
OC_TOKEN_MODE_STOKEN,
OC_TOKEN_MODE_TOTP,
OC_TOKEN_MODE_HOTP,
} oc_token_mode_t;

/* Unless otherwise specified, all functions which set strings will take
Expand Down
1 change: 1 addition & 0 deletions www/changelog.xml
Expand Up @@ -15,6 +15,7 @@
<ul>
<li><b>OpenConnect HEAD</b>
<ul>
<li>Add RFC4226 HOTP token support.</li>
<li>Tolerate servers closing connection uncleanly after HTTP/1.0 response <a href="https://bugs.launchpad.net/bugs/1225276"><i>(Ubuntu #1225276)</i></a>.</li>
<li>Add support for IPv6 split tunnel configuration.</li>
<li>Add Windows support with MinGW <i>(tested with both IPv6 and Legacy IP with latest <a href="http://git.infradead.org/users/dwmw2/vpnc-scripts.git/blob_plain/HEAD:/vpnc-script-win.js">vpnc-script-win.js</a>)</i></li>
Expand Down

0 comments on commit ef31b98

Please sign in to comment.