From 7236d82b1b163551e7b32faa7b6dd8d884af5df7 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Wed, 13 Aug 2014 12:26:01 +0100 Subject: [PATCH] Add PSKC support Signed-off-by: David Woodhouse --- Makefile.am | 6 +-- auth.c | 38 ++++++++++++++++++- configure.ac | 7 ++++ library.c | 83 ++++++++++++++++++++++++++++++++++++++++-- openconnect-internal.h | 8 ++++ openconnect.pc.in | 2 +- www/changelog.xml | 5 ++- 7 files changed, 138 insertions(+), 11 deletions(-) diff --git a/Makefile.am b/Makefile.am index d46447be..7134f23b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -15,7 +15,7 @@ AM_CFLAGS = @WFLAGS@ AM_CPPFLAGS = -DLOCALEDIR="\"$(localedir)\"" openconnect_SOURCES = xml.c main.c -openconnect_CFLAGS = $(AM_CFLAGS) $(SSL_CFLAGS) $(DTLS_SSL_CFLAGS) $(LIBXML2_CFLAGS) $(LIBPROXY_CFLAGS) $(ZLIB_CFLAGS) $(LIBSTOKEN_CFLAGS) $(LIBOATH_CFLAGS) $(GSSAPI_CFLAGS) +openconnect_CFLAGS = $(AM_CFLAGS) $(SSL_CFLAGS) $(DTLS_SSL_CFLAGS) $(LIBXML2_CFLAGS) $(LIBPROXY_CFLAGS) $(ZLIB_CFLAGS) $(LIBSTOKEN_CFLAGS) $(LIBOATH_CFLAGS) $(LIBPSKC_CFLAGS) $(GSSAPI_CFLAGS) openconnect_LDADD = libopenconnect.la $(LIBXML2_LIBS) $(LIBPROXY_LIBS) $(LIBINTL) $(LIBICONV) library_srcs = ssl.c http.c auth.c library.c compat.c dtls.c cstp.c \ @@ -50,8 +50,8 @@ library_srcs += $(lib_srcs_posix) endif libopenconnect_la_SOURCES = version.c $(library_srcs) -libopenconnect_la_CFLAGS = $(AM_CFLAGS) $(SSL_CFLAGS) $(DTLS_SSL_CFLAGS) $(LIBXML2_CFLAGS) $(LIBPROXY_CFLAGS) $(ZLIB_CFLAGS) $(P11KIT_CFLAGS) $(TSS_CFLAGS) $(LIBSTOKEN_CFLAGS) $(LIBOATH_CFLAGS) $(GSSAPI_CFLAGS) -libopenconnect_la_LIBADD = $(SSL_LIBS) $(DTLS_SSL_LIBS) $(LIBXML2_LIBS) $(LIBPROXY_LIBS) $(ZLIB_LIBS) $(LIBINTL) $(P11KIT_LIBS) $(TSS_LIBS) $(LIBSTOKEN_LIBS) $(LIBOATH_LIBS) $(GSSAPI_LIBS) $(LIBICONV) +libopenconnect_la_CFLAGS = $(AM_CFLAGS) $(SSL_CFLAGS) $(DTLS_SSL_CFLAGS) $(LIBXML2_CFLAGS) $(LIBPROXY_CFLAGS) $(ZLIB_CFLAGS) $(P11KIT_CFLAGS) $(TSS_CFLAGS) $(LIBSTOKEN_CFLAGS) $(LIBOATH_CFLAGS) $(LIBPSKC_CFLAGS) $(GSSAPI_CFLAGS) +libopenconnect_la_LIBADD = $(SSL_LIBS) $(DTLS_SSL_LIBS) $(LIBXML2_LIBS) $(LIBPROXY_LIBS) $(ZLIB_LIBS) $(LIBINTL) $(P11KIT_LIBS) $(TSS_LIBS) $(LIBSTOKEN_LIBS) $(LIBOATH_LIBS) $(LIBPSKC_LIBS) $(GSSAPI_LIBS) $(LIBICONV) 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 94909750..a1da6a87 100644 --- a/auth.c +++ b/auth.c @@ -1452,17 +1452,19 @@ static void buf_append_base32(struct oc_text_buf *buf, void *data, int len) static char *regen_hotp_secret(struct openconnect_info *vpninfo) { char *new_secret = NULL; - struct oc_text_buf *buf = buf_alloc(); + 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", @@ -1470,12 +1472,44 @@ static char *regen_hotp_secret(struct openconnect_info *vpninfo) break; case HOTP_SECRET_RAW: + buf = buf_alloc(); buf_append_bytes(buf, vpninfo->oath_secret, vpninfo->oath_secret_len); break; case HOTP_SECRET_PSKC: - /* Not implemented yet */ +#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; } diff --git a/configure.ac b/configure.ac index 895c00c7..8f75a2dd 100644 --- a/configure.ac +++ b/configure.ac @@ -622,6 +622,13 @@ AS_IF([test "x$with_liboath" != "xno"], [ AC_DEFINE([HAVE_LIBOATH], 1, [Have liboath]) liboath_pkg=yes], liboath_pkg=no) + if test "$liboath_pkg" = "yes"; then + PKG_CHECK_MODULES(LIBPSKC, [libpskc >= 2.2.0], + [AC_SUBST(LIBPSKC_PC, libpskc) + AC_DEFINE([HAVE_LIBPSKC], 1, [Have libpskc]) + libpskc_pkg=yes], + libpskc_pkg=no) + fi ]) linked_gssapi=no diff --git a/library.c b/library.c index edc09f3a..a3a1f500 100644 --- a/library.c +++ b/library.c @@ -229,9 +229,16 @@ void openconnect_vpninfo_free(struct openconnect_info *vpninfo) stoken_destroy(vpninfo->stoken_ctx); #endif #ifdef HAVE_LIBOATH - if (vpninfo->oath_secret) + if (vpninfo->oath_secret) { +#ifdef HAVE_LIBPSKC + if (vpninfo->pskc) + pskc_done(vpninfo->pskc); + else +#endif /* HAVE_LIBPSKC */ + free(vpninfo->oath_secret); oath_done(); -#endif + } +#endif /* HAVE_LIBOATH */ /* These check strm->state so they are safe to call multiple times */ inflateEnd(&vpninfo->inflate_strm); @@ -561,6 +568,62 @@ static char *parse_hex(const char *tok, int len) } #endif +#ifdef HAVE_LIBOATH +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 */ +} +#endif /* HAVE_LIBOATH */ + static int set_totp_mode(struct openconnect_info *vpninfo, const char *token_str) { @@ -578,7 +641,12 @@ static int set_totp_mode(struct openconnect_info *vpninfo, while (toklen && isspace((int)(unsigned char)token_str[toklen-1])) toklen--; - if (strncasecmp(token_str, "base32:", strlen("base32:")) == 0) { + 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, @@ -617,6 +685,15 @@ static int set_hotp_mode(struct openconnect_info *vpninfo, 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; diff --git a/openconnect-internal.h b/openconnect-internal.h index 75a8ce78..854cd107 100644 --- a/openconnect-internal.h +++ b/openconnect-internal.h @@ -85,6 +85,10 @@ #include GSSAPI_HDR #endif +#ifdef HAVE_LIBPSKC +#include +#endif + #ifdef ENABLE_NLS #include #define _(s) dgettext("openconnect", s) @@ -265,6 +269,10 @@ struct openconnect_info { int stoken_concat_pin; int stoken_interval; #endif +#ifdef HAVE_LIBPSKC + pskc_t *pskc; + pskc_key_t *pskc_key; +#endif #ifdef HAVE_LIBOATH char *oath_secret; size_t oath_secret_len; diff --git a/openconnect.pc.in b/openconnect.pc.in index 6930a8b6..18b22a3b 100644 --- a/openconnect.pc.in +++ b/openconnect.pc.in @@ -7,7 +7,7 @@ includedir=@includedir@ Name: openconnect Description: OpenConnect VPN client Version: @VERSION@ -Requires.private: @LIBPROXY_PC@ @ZLIB_PC@ @SSL_DTLS_PC@ @P11KIT_PC@ @LIBSTOKEN_PC@ @LIBOATH_PC@ libxml-2.0 +Requires.private: @LIBPROXY_PC@ @ZLIB_PC@ @SSL_DTLS_PC@ @P11KIT_PC@ @LIBSTOKEN_PC@ @LIBOATH_PC@ @LIBPSKC_PC@ libxml-2.0 Libs: -L${libdir} -lopenconnect Libs.private: @LIBINTL@ Cflags: -I${includedir} diff --git a/www/changelog.xml b/www/changelog.xml index 5621591b..9033b442 100644 --- a/www/changelog.xml +++ b/www/changelog.xml @@ -15,6 +15,7 @@
  • OpenConnect HEAD
      +
    • Support using PSKC (RFC6030) token files for HOTP/TOTP tokens.
    • Support for updating HOTP token storage when token is used.
    • Support for reading OTP token data from a file.
    • Add full character set handling for legacy non-UTF8 systems (including Windows).
    • @@ -44,7 +45,7 @@
    • OpenConnect v5.99 (PGP signature) — 2014-03-05
        -
      • Add RFC4226 HOTP token support.
      • +
      • Add RFC4226 HOTP token support.
      • Tolerate servers closing connection uncleanly after HTTP/1.0 response (Ubuntu #1225276).
      • Add support for IPv6 split tunnel configuration.
      • Add Windows support with MinGW (tested with both IPv6 and Legacy IP with latest vpnc-script-win.js)
      • @@ -106,7 +107,7 @@
      • Fix compatibility issues with XML POST authentication.
      • Fix memory leaks on realloc() failure.
      • Fix certificate validation problem caused by hostname canonicalisation.
      • -
      • Add RFC6238 TOTP token support using liboath.
      • +
      • Add RFC6238 TOTP token support using liboath.
      • Replace --stoken option with more generic --token-mode and --token-secret options.