aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--base64.c2
-rw-r--r--base64.h2
-rw-r--r--misc.c158
-rw-r--r--misc.h38
-rw-r--r--push.c12
-rw-r--r--ssl.c27
-rw-r--r--ssl.h11
-rw-r--r--syshead.h5
-rw-r--r--version.m42
9 files changed, 231 insertions, 26 deletions
diff --git a/base64.c b/base64.c
index 7d876a6..2cc3944 100644
--- a/base64.c
+++ b/base64.c
@@ -33,7 +33,7 @@
#include "syshead.h"
-#if defined(ENABLE_HTTP_PROXY) || defined(ENABLE_PKCS11)
+#if defined(ENABLE_HTTP_PROXY) || defined(ENABLE_PKCS11) || defined(ENABLE_CLIENT_CR)
#include "base64.h"
diff --git a/base64.h b/base64.h
index a966b2d..968d18d 100644
--- a/base64.h
+++ b/base64.h
@@ -34,7 +34,7 @@
#ifndef _BASE64_H_
#define _BASE64_H_
-#ifdef ENABLE_HTTP_PROXY
+#if defined(ENABLE_HTTP_PROXY) || defined(ENABLE_PKCS11) || defined(ENABLE_CLIENT_CR)
int base64_encode(const void *data, int size, char **str);
int base64_decode(const char *str, void *data);
diff --git a/misc.c b/misc.c
index 1e863f2..94e9efe 100644
--- a/misc.c
+++ b/misc.c
@@ -26,6 +26,7 @@
#include "buffer.h"
#include "misc.h"
+#include "base64.h"
#include "tun.h"
#include "error.h"
#include "thread.h"
@@ -1397,10 +1398,11 @@ get_console_input (const char *prompt, const bool echo, char *input, const int c
*/
bool
-get_user_pass (struct user_pass *up,
- const char *auth_file,
- const char *prefix,
- const unsigned int flags)
+get_user_pass_cr (struct user_pass *up,
+ const char *auth_file,
+ const char *prefix,
+ const unsigned int flags,
+ const char *auth_challenge)
{
struct gc_arena gc = gc_new ();
@@ -1413,7 +1415,7 @@ get_user_pass (struct user_pass *up,
#ifdef ENABLE_MANAGEMENT
/*
- * Get username/password from standard input?
+ * Get username/password from management interface?
*/
if (management
&& ((auth_file && streq (auth_file, "management")) || (from_stdin && (flags & GET_USER_PASS_MANAGEMENT)))
@@ -1453,22 +1455,47 @@ get_user_pass (struct user_pass *up,
*/
else if (from_stdin)
{
- struct buffer user_prompt = alloc_buf_gc (128, &gc);
- struct buffer pass_prompt = alloc_buf_gc (128, &gc);
-
- buf_printf (&user_prompt, "Enter %s Username:", prefix);
- buf_printf (&pass_prompt, "Enter %s Password:", prefix);
-
- if (!(flags & GET_USER_PASS_PASSWORD_ONLY))
+#ifdef ENABLE_CLIENT_CR
+ if (auth_challenge)
{
- if (!get_console_input (BSTR (&user_prompt), true, up->username, USER_PASS_LEN))
- msg (M_FATAL, "ERROR: could not read %s username from stdin", prefix);
- if (strlen (up->username) == 0)
- msg (M_FATAL, "ERROR: %s username is empty", prefix);
+ struct auth_challenge_info *ac = get_auth_challenge (auth_challenge, &gc);
+ if (ac)
+ {
+ char *response = (char *) gc_malloc (USER_PASS_LEN, false, &gc);
+ struct buffer packed_resp;
+
+ buf_set_write (&packed_resp, (uint8_t*)up->password, USER_PASS_LEN);
+ msg (M_INFO, "CHALLENGE: %s", ac->challenge_text);
+ if (!get_console_input ("Response:", BOOL_CAST(ac->flags&CR_ECHO), response, USER_PASS_LEN))
+ msg (M_FATAL, "ERROR: could not read challenge response from stdin");
+ strncpynt (up->username, ac->user, USER_PASS_LEN);
+ buf_printf (&packed_resp, "CRV1::%s::%s", ac->state_id, response);
+ }
+ else
+ {
+ msg (M_FATAL, "ERROR: received malformed challenge request from server");
+ }
}
+ else
+#endif
+ {
+ struct buffer user_prompt = alloc_buf_gc (128, &gc);
+ struct buffer pass_prompt = alloc_buf_gc (128, &gc);
- if (!get_console_input (BSTR (&pass_prompt), false, up->password, USER_PASS_LEN))
- msg (M_FATAL, "ERROR: could not not read %s password from stdin", prefix);
+ buf_printf (&user_prompt, "Enter %s Username:", prefix);
+ buf_printf (&pass_prompt, "Enter %s Password:", prefix);
+
+ if (!(flags & GET_USER_PASS_PASSWORD_ONLY))
+ {
+ if (!get_console_input (BSTR (&user_prompt), true, up->username, USER_PASS_LEN))
+ msg (M_FATAL, "ERROR: could not read %s username from stdin", prefix);
+ if (strlen (up->username) == 0)
+ msg (M_FATAL, "ERROR: %s username is empty", prefix);
+ }
+
+ if (!get_console_input (BSTR (&pass_prompt), false, up->password, USER_PASS_LEN))
+ msg (M_FATAL, "ERROR: could not not read %s password from stdin", prefix);
+ }
}
else
{
@@ -1532,6 +1559,101 @@ get_user_pass (struct user_pass *up,
return true;
}
+#ifdef ENABLE_CLIENT_CR
+
+/*
+ * Parse a challenge message returned along with AUTH_FAILED.
+ * The message is formatted as such:
+ *
+ * CRV1:<flags>:<state_id>:<username_base64>:<challenge_text>
+ *
+ * flags: a series of optional, comma-separated flags:
+ * E : echo the response when the user types it
+ * R : a response is required
+ *
+ * state_id: an opaque string that should be returned to the server
+ * along with the response.
+ *
+ * username_base64 : the username formatted as base64
+ *
+ * challenge_text : the challenge text to be shown to the user
+ *
+ * Example challenge:
+ *
+ * CRV1:R,E:Om01u7Fh4LrGBS7uh0SWmzwabUiGiW6l:Y3Ix:Please enter token PIN
+ *
+ * After showing the challenge_text and getting a response from the user
+ * (if R flag is specified), the client should submit the following
+ * auth creds back to the OpenVPN server:
+ *
+ * Username: [username decoded from username_base64]
+ * Password: CRV1::<state_id>::<response_text>
+ *
+ * Where state_id is taken from the challenge request and response_text
+ * is what the user entered in response to the challenge_text.
+ * If the R flag is not present, response_text may be the empty
+ * string.
+ *
+ * Example response (suppose the user enters "8675309" for the token PIN):
+ *
+ * Username: cr1 ("Y3Ix" base64 decoded)
+ * Password: CRV1::Om01u7Fh4LrGBS7uh0SWmzwabUiGiW6l::8675309
+ */
+struct auth_challenge_info *
+get_auth_challenge (const char *auth_challenge, struct gc_arena *gc)
+{
+ if (auth_challenge)
+ {
+ struct auth_challenge_info *ac;
+ const int len = strlen (auth_challenge);
+ char *work = (char *) gc_malloc (len+1, false, gc);
+ char *cp;
+
+ struct buffer b;
+ buf_set_read (&b, (const uint8_t *)auth_challenge, len);
+
+ ALLOC_OBJ_CLEAR_GC (ac, struct auth_challenge_info, gc);
+
+ /* parse prefix */
+ if (!buf_parse(&b, ':', work, len))
+ return NULL;
+ if (strcmp(work, "CRV1"))
+ return NULL;
+
+ /* parse flags */
+ if (!buf_parse(&b, ':', work, len))
+ return NULL;
+ for (cp = work; *cp != '\0'; ++cp)
+ {
+ const char c = *cp;
+ if (c == 'E')
+ ac->flags |= CR_ECHO;
+ else if (c == 'R')
+ ac->flags |= CR_RESPONSE;
+ }
+
+ /* parse state ID */
+ if (!buf_parse(&b, ':', work, len))
+ return NULL;
+ ac->state_id = string_alloc(work, gc);
+
+ /* parse user name */
+ if (!buf_parse(&b, ':', work, len))
+ return NULL;
+ ac->user = (char *) gc_malloc (strlen(work)+1, true, gc);
+ base64_decode(work, (void*)ac->user);
+
+ /* parse challenge text */
+ ac->challenge_text = string_alloc(BSTR(&b), gc);
+
+ return ac;
+ }
+ else
+ return NULL;
+}
+
+#endif
+
#if AUTO_USERID
static const char *
diff --git a/misc.h b/misc.h
index 7db9332..3f22ca0 100644
--- a/misc.h
+++ b/misc.h
@@ -261,6 +261,26 @@ struct user_pass
char password[USER_PASS_LEN];
};
+#ifdef ENABLE_CLIENT_CR
+/*
+ * Challenge response info on client as pushed by server.
+ */
+struct auth_challenge_info {
+# define CR_ECHO (1<<0) /* echo response when typed by user */
+# define CR_RESPONSE (1<<1) /* response needed */
+ unsigned int flags;
+
+ const char *user;
+ const char *state_id;
+ const char *challenge_text;
+};
+
+struct auth_challenge_info *get_auth_challenge (const char *auth_challenge, struct gc_arena *gc);
+
+#else
+struct auth_challenge_info {};
+#endif
+
bool get_console_input (const char *prompt, const bool echo, char *input, const int capacity);
/*
@@ -274,10 +294,20 @@ bool get_console_input (const char *prompt, const bool echo, char *input, const
#define GET_USER_PASS_NEED_STR (1<<5)
#define GET_USER_PASS_PREVIOUS_CREDS_FAILED (1<<6)
-bool get_user_pass (struct user_pass *up,
- const char *auth_file,
- const char *prefix,
- const unsigned int flags);
+bool get_user_pass_cr (struct user_pass *up,
+ const char *auth_file,
+ const char *prefix,
+ const unsigned int flags,
+ const char *auth_challenge);
+
+static inline bool
+get_user_pass (struct user_pass *up,
+ const char *auth_file,
+ const char *prefix,
+ const unsigned int flags)
+{
+ return get_user_pass_cr (up, auth_file, prefix, flags, NULL);
+}
void fail_user_pass (const char *prefix,
const unsigned int flags,
diff --git a/push.c b/push.c
index 40e64c3..08c7f99 100644
--- a/push.c
+++ b/push.c
@@ -68,8 +68,18 @@ receive_auth_failed (struct context *c, const struct buffer *buffer)
if (buf_string_compare_advance (&buf, "AUTH_FAILED,") && BLEN (&buf))
reason = BSTR (&buf);
management_auth_failure (management, UP_TYPE_AUTH, reason);
- }
+ } else
#endif
+ {
+#ifdef ENABLE_CLIENT_CR
+ struct buffer buf = *buffer;
+ if (buf_string_match_head_str (&buf, "AUTH_FAILED,CRV1:") && BLEN (&buf))
+ {
+ buf_advance (&buf, 12); /* Length of "AUTH_FAILED," substring */
+ ssl_put_auth_challenge (BSTR (&buf));
+ }
+#endif
+ }
}
}
diff --git a/ssl.c b/ssl.c
index e7cb46a..93eed18 100644
--- a/ssl.c
+++ b/ssl.c
@@ -290,6 +290,10 @@ pem_password_callback (char *buf, int size, int rwflag, void *u)
static bool auth_user_pass_enabled; /* GLOBAL */
static struct user_pass auth_user_pass; /* GLOBAL */
+#ifdef ENABLE_CLIENT_CR
+static char *auth_challenge; /* GLOBAL */
+#endif
+
void
auth_user_pass_setup (const char *auth_file)
{
@@ -298,6 +302,8 @@ auth_user_pass_setup (const char *auth_file)
{
#if AUTO_USERID
get_user_pass_auto_userid (&auth_user_pass, auth_file);
+#elif defined(ENABLE_CLIENT_CR)
+ get_user_pass_cr (&auth_user_pass, auth_file, UP_TYPE_AUTH, GET_USER_PASS_MANAGEMENT|GET_USER_PASS_SENSITIVE, auth_challenge);
#else
get_user_pass (&auth_user_pass, auth_file, UP_TYPE_AUTH, GET_USER_PASS_MANAGEMENT|GET_USER_PASS_SENSITIVE);
#endif
@@ -325,8 +331,29 @@ ssl_purge_auth (void)
#endif
purge_user_pass (&passbuf, true);
purge_user_pass (&auth_user_pass, true);
+#ifdef ENABLE_CLIENT_CR
+ ssl_purge_auth_challenge();
+#endif
+}
+
+#ifdef ENABLE_CLIENT_CR
+
+void
+ssl_purge_auth_challenge (void)
+{
+ free (auth_challenge);
+ auth_challenge = NULL;
}
+void
+ssl_put_auth_challenge (const char *cr_str)
+{
+ ssl_purge_auth_challenge();
+ auth_challenge = string_alloc(cr_str, NULL);
+}
+
+#endif
+
/*
* OpenSSL callback to get a temporary RSA key, mostly
* used for export ciphers.
diff --git a/ssl.h b/ssl.h
index 8415d55..f159fdd 100644
--- a/ssl.h
+++ b/ssl.h
@@ -709,6 +709,17 @@ void auth_user_pass_setup (const char *auth_file);
void ssl_set_auth_nocache (void);
void ssl_purge_auth (void);
+
+#ifdef ENABLE_CLIENT_CR
+/*
+ * ssl_get_auth_challenge will parse the server-pushed auth-failed
+ * reason string and return a dynamically allocated
+ * auth_challenge_info struct.
+ */
+void ssl_purge_auth_challenge (void);
+void ssl_put_auth_challenge (const char *cr_str);
+#endif
+
void tls_set_verify_command (const char *cmd);
void tls_set_crl_verify (const char *crl);
void tls_set_verify_x509name (const char *x509name);
diff --git a/syshead.h b/syshead.h
index 1b8bfad..6479b20 100644
--- a/syshead.h
+++ b/syshead.h
@@ -665,6 +665,11 @@ socket_defined (const socket_descriptor_t sd)
#endif
/*
+ * Do we support challenge/response authentication, as a console-based client?
+ */
+#define ENABLE_CLIENT_CR
+
+/*
* Do we support pushing peer info?
*/
#define ENABLE_PUSH_PEER_INFO
diff --git a/version.m4 b/version.m4
index 0dd97ae..f390f4c 100644
--- a/version.m4
+++ b/version.m4
@@ -1,5 +1,5 @@
dnl define the OpenVPN version
-define(PRODUCT_VERSION,[2.2-beta3])
+define(PRODUCT_VERSION,[2.2-beta])
dnl define the TAP version
define(PRODUCT_TAP_ID,[tap0901])
define(PRODUCT_TAP_WIN32_MIN_MAJOR,[9])