diff --git a/Makefile.am b/Makefile.am index 7134f23b..68452fcd 100644 --- a/Makefile.am +++ b/Makefile.am @@ -26,11 +26,15 @@ lib_srcs_win32 = tun-win32.c sspi.c lib_srcs_posix = tun.c lib_srcs_gssapi = gssapi.c lib_srcs_iconv = iconv.c +lib_srcs_oath = oath.c POTFILES = $(openconnect_SOURCES) $(lib_srcs_openssl) $(lib_srcs_gnutls) \ $(library_srcs) $(lib_srcs_win32) $(lib_srcs_posix) $(lib_srcs_gssapi) \ - $(lib_srcs_iconv) openconnect-internal.h + $(lib_srcs_iconv) $(lib_srcs_oath) openconnect-internal.h +if OPENCONNECT_OATH +library_srcs += $(lib_srcs_oath) +endif if OPENCONNECT_GSSAPI library_srcs += $(lib_srcs_gssapi) endif diff --git a/auth.c b/auth.c index 77dcdd2f..a2d267c7 100644 --- a/auth.c +++ b/auth.c @@ -1315,61 +1315,6 @@ static int do_gen_stoken_code(struct openconnect_info *vpninfo, #endif -#ifdef HAVE_LIBOATH -/* Return value: - * < 0, if unable to generate a tokencode - * = 0, on success - */ -static int can_gen_totp_code(struct openconnect_info *vpninfo, - struct oc_auth_form *form, - struct oc_form_opt *opt) -{ - 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; -} - -/* 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) -{ - 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")); - } 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; -} -#endif /* Return value: * < 0, if unable to generate a tokencode * = 0, on success @@ -1394,166 +1339,6 @@ static int can_gen_tokencode(struct openconnect_info *vpninfo, } } -#ifdef HAVE_LIBOATH -static int do_gen_totp_code(struct openconnect_info *vpninfo, - struct oc_auth_form *form, - struct oc_form_opt *opt) -{ - 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; -} - -static void buf_append_base32(struct oc_text_buf *buf, void *data, int len) -{ - size_t b32_len; - char *b32 = NULL; - - if (oath_base32_encode(data, len, &b32, &b32_len)) { - buf->error = ENOMEM; - return; - } - buf_append_bytes(buf, b32, b32_len); - free(b32); -} - -static char *regen_hotp_secret(struct openconnect_info *vpninfo) -{ - char *new_secret = NULL; - struct oc_text_buf *buf; - int i; - - switch (vpninfo->hotp_secret_format) { - case HOTP_SECRET_BASE32: - buf = buf_alloc(); - buf_append(buf, "base32:"); - buf_append_base32(buf, vpninfo->oath_secret, - vpninfo->oath_secret_len); - break; - - case HOTP_SECRET_HEX: - buf = buf_alloc(); - buf_append(buf, "0x"); - for (i=0; i < vpninfo->oath_secret_len; i++) - buf_append(buf, "%02x", - (unsigned char)vpninfo->oath_secret[i]); - break; - - case HOTP_SECRET_RAW: - buf = buf_alloc(); - buf_append_bytes(buf, vpninfo->oath_secret, - vpninfo->oath_secret_len); - break; - - case HOTP_SECRET_PSKC: -#ifdef HAVE_LIBPSKC - { - size_t len; - if (!vpninfo->pskc_key || !vpninfo->pskc) - return NULL; - pskc_set_key_data_counter(vpninfo->pskc_key, vpninfo->token_time); - pskc_build_xml(vpninfo->pskc, &new_secret, &len); - /* FFS #1: libpskc craps all over itself on pskc_build_xml(). - https://bugzilla.redhat.com/show_bug.cgi?id=1129491 - Hopefully this will be fixed by 2.4.2 but make it - unconditional for now... */ - if (1 || !pskc_check_version("2.4.2")) { - pskc_done(vpninfo->pskc); - vpninfo->pskc = NULL; - vpninfo->pskc_key = NULL; - if (pskc_init(&vpninfo->pskc) || - pskc_parse_from_memory(vpninfo->pskc, len, new_secret)) { - pskc_done(vpninfo->pskc); - vpninfo->pskc = NULL; - } else { - vpninfo->pskc_key = pskc_get_keypackage(vpninfo->pskc, 0); - vpninfo->oath_secret = (char *)pskc_get_key_data_secret(vpninfo->pskc_key, NULL); - } - } - /* FFS #2: No terminating NUL byte */ - realloc_inplace(new_secret, len + 1); - if (new_secret) - new_secret[len] = 0; - return new_secret; - } -#endif - default: - return NULL; - } - - buf_append(buf,",%ld", (long)vpninfo->token_time); - if (!buf_error(buf)) { - new_secret = buf->data; - buf->data = NULL; - } - buf_free(buf); - return new_secret; -} - -static int do_gen_hotp_code(struct openconnect_info *vpninfo, - struct oc_auth_form *form, - struct oc_form_opt *opt) -{ - int oath_err; - char tokencode[7]; - int ret; - - vpn_progress(vpninfo, PRG_INFO, _("Generating OATH HOTP token code\n")); - - if (vpninfo->lock_token) { - /* This may call openconnect_set_token_mode() again to update - * the token if it's changed. */ - ret = vpninfo->lock_token(vpninfo->tok_cbdata); - if (ret) - return ret; - } - - 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)); - if (vpninfo->unlock_token) - vpninfo->unlock_token(vpninfo->tok_cbdata, NULL); - return -EIO; - } - vpninfo->token_time++; - vpninfo->token_tries++; - opt->value = strdup(tokencode); - if (vpninfo->unlock_token) { - char *new_tok = regen_hotp_secret(vpninfo); - vpninfo->unlock_token(vpninfo->tok_cbdata, new_tok); - free(new_tok); - } - return opt->value ? 0 : -ENOMEM; -} -#endif /* HAVE_LIBOATH */ - /* Return value: * < 0, if unable to generate a tokencode * = 0, on success diff --git a/configure.ac b/configure.ac index 8f75a2dd..b7800ee8 100644 --- a/configure.ac +++ b/configure.ac @@ -630,6 +630,7 @@ AS_IF([test "x$with_liboath" != "xno"], [ libpskc_pkg=no) fi ]) +AM_CONDITIONAL(OPENCONNECT_OATH, [test "$liboath_pkg" = "yes"]) linked_gssapi=no AC_ARG_WITH([gssapi], diff --git a/library.c b/library.c index bd7635ab..f9384f42 100644 --- a/library.c +++ b/library.c @@ -531,207 +531,6 @@ static int set_libstoken_mode(struct openconnect_info *vpninfo, #endif } -#ifdef HAVE_LIBOATH -static char *parse_hex(const char *tok, int len) -{ - unsigned char *data, *p; - - data = malloc((len + 1) / 2); - if (!data) - return NULL; - - p = data; - - if (len & 1) { - char b[2] = { '0', tok[0] }; - if (!isxdigit((int)(unsigned char)tok[0])) { - free(data); - return NULL; - } - *(p++) = unhex(b); - tok++; - len--; - } - - while (len) { - if (!isxdigit((int)(unsigned char)tok[0]) || - !isxdigit((int)(unsigned char)tok[1])) { - free(data); - return NULL; - } - *(p++) = unhex(tok); - tok += 2; - len -= 2; - } - - return (char *)data; -} - -static int pskc_decode(struct openconnect_info *vpninfo, const char *token_str, - int toklen, int mode) -{ -#ifdef HAVE_LIBPSKC - pskc_t *container; - pskc_key_t *key; - const char *key_algo; - const char *want_algo; - size_t klen; - - if (pskc_global_init()) - return -EIO; - - if (pskc_init(&container)) - return -ENOMEM; - - if (pskc_parse_from_memory(container, toklen, token_str)) - return -EINVAL; - - key = pskc_get_keypackage(container, 0); - if (!key) { - pskc_done(container); - return -EINVAL; - } - if (mode == OC_TOKEN_MODE_HOTP) - want_algo = "urn:ietf:params:xml:ns:keyprov:pskc:hotp"; - else - want_algo = "urn:ietf:params:xml:ns:keyprov:pskc:totp"; - key_algo = pskc_get_key_algorithm(key); - - if (!key_algo || strcmp(key_algo, want_algo)) { - pskc_done(container); - return -EINVAL; - } - - vpninfo->oath_secret = (char *)pskc_get_key_data_secret(key, &klen); - vpninfo->oath_secret_len = klen; - if (!vpninfo->oath_secret) { - pskc_done(container); - return -EINVAL; - } - vpninfo->token_time = pskc_get_key_data_counter(key, NULL); - - vpninfo->pskc = container; - vpninfo->pskc_key = key; - - return 0; -#else /* !HAVE_LIBPSKC */ - vpn_progress(vpninfo, PRG_ERR, - _("This version of OpenConnect was built without PSKC support\n")); - return -EINVAL; -#endif /* HAVE_LIBPSKC */ -} - -static int set_totp_mode(struct openconnect_info *vpninfo, - const char *token_str) -{ - int ret, toklen; - - ret = oath_init(); - if (ret != OATH_OK) - return -EIO; - - if (!token_str) - return -EINVAL; - - toklen = strlen(token_str); - while (toklen && isspace((int)(unsigned char)token_str[toklen-1])) - toklen--; - - if (strncmp(token_str, "hotp_secret_format = HOTP_SECRET_PSKC; - ret = pskc_decode(vpninfo, token_str, toklen, OC_TOKEN_MODE_TOTP); - if (ret) - return -EINVAL; - } else 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 if (strncmp(token_str, "0x", 2) == 0) { - vpninfo->oath_secret_len = (toklen - 2) / 2; - vpninfo->oath_secret = parse_hex(token_str + 2, toklen - 2); - if (!vpninfo->oath_secret) - return -EINVAL; - } else { - vpninfo->oath_secret = strdup(token_str); - vpninfo->oath_secret_len = toklen; - } - - vpninfo->token_mode = OC_TOKEN_MODE_TOTP; - return 0; -} - -static int set_hotp_mode(struct openconnect_info *vpninfo, - const char *token_str) -{ - int ret, toklen; - char *p; - - ret = oath_init(); - if (ret != OATH_OK) - return -EIO; - - if (!token_str) - return -EINVAL; - - toklen = strlen(token_str); - - if (strncmp(token_str, "hotp_secret_format = HOTP_SECRET_PSKC; - ret = pskc_decode(vpninfo, token_str, toklen, OC_TOKEN_MODE_HOTP); - if (ret) - return -EINVAL; - vpninfo->token_mode = OC_TOKEN_MODE_HOTP; - return 0; - } - p = strrchr(token_str, ','); - if (p) { - long counter; - toklen = p - token_str; - p++; - counter = strtol(p, &p, 0); - if (counter < 0) - return -EINVAL; - while (*p) { - if (isspace((int)(unsigned char)*p)) - p++; - else - return -EINVAL; - } - vpninfo->token_time = counter; - } else { - while (toklen && - isspace((int)(unsigned char)token_str[toklen-1])) - toklen--; - } - - if (strncasecmp(token_str, "base32:", strlen("base32:")) == 0) { - vpninfo->hotp_secret_format = HOTP_SECRET_BASE32; - 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 if (strncmp(token_str, "0x", 2) == 0) { - vpninfo->hotp_secret_format = HOTP_SECRET_HEX; - vpninfo->oath_secret_len = (toklen - 2) / 2; - vpninfo->oath_secret = parse_hex(token_str + 2, toklen - 2); - if (!vpninfo->oath_secret) - return -EINVAL; - } else { - vpninfo->hotp_secret_format = HOTP_SECRET_RAW; - vpninfo->oath_secret = strdup(token_str); - vpninfo->oath_secret_len = toklen; - } - - vpninfo->token_mode = OC_TOKEN_MODE_HOTP; - return 0; -} -#endif /* HAVE_LIBOATH */ - int openconnect_set_token_callbacks(struct openconnect_info *vpninfo, void *tokdata, openconnect_lock_token_vfn lock, diff --git a/oath.c b/oath.c new file mode 100644 index 00000000..092f641d --- /dev/null +++ b/oath.c @@ -0,0 +1,437 @@ +/* + * OpenConnect (SSL + DTLS) VPN client + * + * Copyright © 2008-2014 Intel Corporation. + * Copyright © 2013 John Morrissey + + * Author: David Woodhouse + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * version 2.1, as published by the Free Software Foundation. + * + * 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 + * Lesser General Public License for more details. + */ + +#include + +#include +#include +#include +#include + +#include + +#include "openconnect-internal.h" + +static char *parse_hex(const char *tok, int len) +{ + unsigned char *data, *p; + + data = malloc((len + 1) / 2); + if (!data) + return NULL; + + p = data; + + if (len & 1) { + char b[2] = { '0', tok[0] }; + if (!isxdigit((int)(unsigned char)tok[0])) { + free(data); + return NULL; + } + *(p++) = unhex(b); + tok++; + len--; + } + + while (len) { + if (!isxdigit((int)(unsigned char)tok[0]) || + !isxdigit((int)(unsigned char)tok[1])) { + free(data); + return NULL; + } + *(p++) = unhex(tok); + tok += 2; + len -= 2; + } + + return (char *)data; +} + +static int pskc_decode(struct openconnect_info *vpninfo, const char *token_str, + int toklen, int mode) +{ +#ifdef HAVE_LIBPSKC + pskc_t *container; + pskc_key_t *key; + const char *key_algo; + const char *want_algo; + size_t klen; + + if (pskc_global_init()) + return -EIO; + + if (pskc_init(&container)) + return -ENOMEM; + + if (pskc_parse_from_memory(container, toklen, token_str)) + return -EINVAL; + + key = pskc_get_keypackage(container, 0); + if (!key) { + pskc_done(container); + return -EINVAL; + } + if (mode == OC_TOKEN_MODE_HOTP) + want_algo = "urn:ietf:params:xml:ns:keyprov:pskc:hotp"; + else + want_algo = "urn:ietf:params:xml:ns:keyprov:pskc:totp"; + key_algo = pskc_get_key_algorithm(key); + + if (!key_algo || strcmp(key_algo, want_algo)) { + pskc_done(container); + return -EINVAL; + } + + vpninfo->oath_secret = (char *)pskc_get_key_data_secret(key, &klen); + vpninfo->oath_secret_len = klen; + if (!vpninfo->oath_secret) { + pskc_done(container); + return -EINVAL; + } + vpninfo->token_time = pskc_get_key_data_counter(key, NULL); + + vpninfo->pskc = container; + vpninfo->pskc_key = key; + + return 0; +#else /* !HAVE_LIBPSKC */ + vpn_progress(vpninfo, PRG_ERR, + _("This version of OpenConnect was built without PSKC support\n")); + return -EINVAL; +#endif /* HAVE_LIBPSKC */ +} + +int set_totp_mode(struct openconnect_info *vpninfo, const char *token_str) +{ + int ret, toklen; + + ret = oath_init(); + if (ret != OATH_OK) + return -EIO; + + if (!token_str) + return -EINVAL; + + toklen = strlen(token_str); + while (toklen && isspace((int)(unsigned char)token_str[toklen-1])) + toklen--; + + if (strncmp(token_str, "hotp_secret_format = HOTP_SECRET_PSKC; + ret = pskc_decode(vpninfo, token_str, toklen, OC_TOKEN_MODE_TOTP); + if (ret) + return -EINVAL; + } else 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 if (strncmp(token_str, "0x", 2) == 0) { + vpninfo->oath_secret_len = (toklen - 2) / 2; + vpninfo->oath_secret = parse_hex(token_str + 2, toklen - 2); + if (!vpninfo->oath_secret) + return -EINVAL; + } else { + vpninfo->oath_secret = strdup(token_str); + vpninfo->oath_secret_len = toklen; + } + + vpninfo->token_mode = OC_TOKEN_MODE_TOTP; + return 0; +} + +int set_hotp_mode(struct openconnect_info *vpninfo, const char *token_str) +{ + int ret, toklen; + char *p; + + ret = oath_init(); + if (ret != OATH_OK) + return -EIO; + + if (!token_str) + return -EINVAL; + + toklen = strlen(token_str); + + if (strncmp(token_str, "hotp_secret_format = HOTP_SECRET_PSKC; + ret = pskc_decode(vpninfo, token_str, toklen, OC_TOKEN_MODE_HOTP); + if (ret) + return -EINVAL; + vpninfo->token_mode = OC_TOKEN_MODE_HOTP; + return 0; + } + p = strrchr(token_str, ','); + if (p) { + long counter; + toklen = p - token_str; + p++; + counter = strtol(p, &p, 0); + if (counter < 0) + return -EINVAL; + while (*p) { + if (isspace((int)(unsigned char)*p)) + p++; + else + return -EINVAL; + } + vpninfo->token_time = counter; + } else { + while (toklen && + isspace((int)(unsigned char)token_str[toklen-1])) + toklen--; + } + + if (strncasecmp(token_str, "base32:", strlen("base32:")) == 0) { + vpninfo->hotp_secret_format = HOTP_SECRET_BASE32; + 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 if (strncmp(token_str, "0x", 2) == 0) { + vpninfo->hotp_secret_format = HOTP_SECRET_HEX; + vpninfo->oath_secret_len = (toklen - 2) / 2; + vpninfo->oath_secret = parse_hex(token_str + 2, toklen - 2); + if (!vpninfo->oath_secret) + return -EINVAL; + } else { + vpninfo->hotp_secret_format = HOTP_SECRET_RAW; + vpninfo->oath_secret = strdup(token_str); + vpninfo->oath_secret_len = toklen; + } + + vpninfo->token_mode = OC_TOKEN_MODE_HOTP; + return 0; +} + +/* Return value: + * < 0, if unable to generate a tokencode + * = 0, on success + */ +int can_gen_totp_code(struct openconnect_info *vpninfo, + struct oc_auth_form *form, + struct oc_form_opt *opt) +{ + 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; +} + +/* Return value: + * < 0, if unable to generate a tokencode + * = 0, on success + */ +int can_gen_hotp_code(struct openconnect_info *vpninfo, + struct oc_auth_form *form, + struct oc_form_opt *opt) +{ + 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")); + } 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; +} + +int do_gen_totp_code(struct openconnect_info *vpninfo, + struct oc_auth_form *form, + struct oc_form_opt *opt) +{ + 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; +} + +static void buf_append_base32(struct oc_text_buf *buf, void *data, int len) +{ + size_t b32_len; + char *b32 = NULL; + + if (oath_base32_encode(data, len, &b32, &b32_len)) { + buf->error = ENOMEM; + return; + } + buf_append_bytes(buf, b32, b32_len); + free(b32); +} + +static char *regen_hotp_secret(struct openconnect_info *vpninfo) +{ + char *new_secret = NULL; + struct oc_text_buf *buf; + int i; + + switch (vpninfo->hotp_secret_format) { + case HOTP_SECRET_BASE32: + buf = buf_alloc(); + buf_append(buf, "base32:"); + buf_append_base32(buf, vpninfo->oath_secret, + vpninfo->oath_secret_len); + break; + + case HOTP_SECRET_HEX: + buf = buf_alloc(); + buf_append(buf, "0x"); + for (i=0; i < vpninfo->oath_secret_len; i++) + buf_append(buf, "%02x", + (unsigned char)vpninfo->oath_secret[i]); + break; + + case HOTP_SECRET_RAW: + buf = buf_alloc(); + buf_append_bytes(buf, vpninfo->oath_secret, + vpninfo->oath_secret_len); + break; + + case HOTP_SECRET_PSKC: +#ifdef HAVE_LIBPSKC + { + size_t len; + if (!vpninfo->pskc_key || !vpninfo->pskc) + return NULL; + pskc_set_key_data_counter(vpninfo->pskc_key, vpninfo->token_time); + pskc_build_xml(vpninfo->pskc, &new_secret, &len); + /* FFS #1: libpskc craps all over itself on pskc_build_xml(). + https://bugzilla.redhat.com/show_bug.cgi?id=1129491 + Hopefully this will be fixed by 2.4.2 but make it + unconditional for now... */ + if (1 || !pskc_check_version("2.4.2")) { + pskc_done(vpninfo->pskc); + vpninfo->pskc = NULL; + vpninfo->pskc_key = NULL; + if (pskc_init(&vpninfo->pskc) || + pskc_parse_from_memory(vpninfo->pskc, len, new_secret)) { + pskc_done(vpninfo->pskc); + vpninfo->pskc = NULL; + } else { + vpninfo->pskc_key = pskc_get_keypackage(vpninfo->pskc, 0); + vpninfo->oath_secret = (char *)pskc_get_key_data_secret(vpninfo->pskc_key, NULL); + } + } + /* FFS #2: No terminating NUL byte */ + realloc_inplace(new_secret, len + 1); + if (new_secret) + new_secret[len] = 0; + return new_secret; + } +#endif + default: + return NULL; + } + + buf_append(buf,",%ld", (long)vpninfo->token_time); + if (!buf_error(buf)) { + new_secret = buf->data; + buf->data = NULL; + } + buf_free(buf); + return new_secret; +} + +int do_gen_hotp_code(struct openconnect_info *vpninfo, + struct oc_auth_form *form, + struct oc_form_opt *opt) +{ + int oath_err; + char tokencode[7]; + int ret; + + vpn_progress(vpninfo, PRG_INFO, _("Generating OATH HOTP token code\n")); + + if (vpninfo->lock_token) { + /* This may call openconnect_set_token_mode() again to update + * the token if it's changed. */ + ret = vpninfo->lock_token(vpninfo->tok_cbdata); + if (ret) + return ret; + } + + 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)); + if (vpninfo->unlock_token) + vpninfo->unlock_token(vpninfo->tok_cbdata, NULL); + return -EIO; + } + vpninfo->token_time++; + vpninfo->token_tries++; + opt->value = strdup(tokencode); + if (vpninfo->unlock_token) { + char *new_tok = regen_hotp_secret(vpninfo); + vpninfo->unlock_token(vpninfo->tok_cbdata, new_tok); + free(new_tok); + } + return opt->value ? 0 : -ENOMEM; +} diff --git a/openconnect-internal.h b/openconnect-internal.h index 854cd107..3062a29d 100644 --- a/openconnect-internal.h +++ b/openconnect-internal.h @@ -645,6 +645,22 @@ ssize_t read_file_into_string(struct openconnect_info *vpninfo, const char *fnam char **ptr); int config_lookup_host(struct openconnect_info *vpninfo, const char *host); +/* oath.c */ +int set_totp_mode(struct openconnect_info *vpninfo, const char *token_str); +int set_hotp_mode(struct openconnect_info *vpninfo, const char *token_str); +int can_gen_totp_code(struct openconnect_info *vpninfo, + struct oc_auth_form *form, + struct oc_form_opt *opt); +int can_gen_hotp_code(struct openconnect_info *vpninfo, + struct oc_auth_form *form, + struct oc_form_opt *opt); +int do_gen_totp_code(struct openconnect_info *vpninfo, + struct oc_auth_form *form, + struct oc_form_opt *opt); +int do_gen_hotp_code(struct openconnect_info *vpninfo, + struct oc_auth_form *form, + struct oc_form_opt *opt); + /* auth.c */ int parse_xml_response(struct openconnect_info *vpninfo, char *response, struct oc_auth_form **form, int *cert_rq);