]> git.ipfire.org Git - thirdparty/openvpn.git/commitdiff
Implement challenge/response authentication support in client mode,
authorJames Yonan <james@openvpn.net>
Sun, 24 Oct 2010 09:12:47 +0000 (09:12 +0000)
committerJames Yonan <james@openvpn.net>
Sun, 24 Oct 2010 09:12:47 +0000 (09:12 +0000)
where credentials are entered from stdin.  This capability is
compiled when ENABLE_CLIENT_CR is defined in syshead.h (enabled
by default).

Challenge/response support was previously implemented for creds
that are queried via the management interface.  In this case,
the challenge message will be returned as a custom
client-reason-text string (see management-notes.txt for more
info) on auth failure.

Also, see the comments in misc.c above get_auth_challenge()
for info on the OpenVPN challenge/response protocol.

git-svn-id: http://svn.openvpn.net/projects/openvpn/branches/BETA21/openvpn@6568 e7ae566f-a301-0410-adde-c780ea21d3b5

base64.c
misc.c
misc.h
push.c
ssl.c
ssl.h
syshead.h

index 7d876a67d1a5834c60f19a073f91f7e13703ca9b..2cc39444e90af7ea872186214c3b49e29054908e 100644 (file)
--- 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/misc.c b/misc.c
index 507bcc2ca17d9a93ccef1288f6d193f40afe496d..f5a3b89b47c79061df530b7000e338b39fede872 100644 (file)
--- 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"
@@ -1363,10 +1364,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 ();
 
@@ -1379,7 +1381,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)))
@@ -1419,22 +1421,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
        {
@@ -1498,6 +1525,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 328107dedf90c0f446bb38a88b5d306878fd91ff..3cd7d9e5e204bfbdba1c50337be59dc3dd0c274d 100644 (file)
--- a/misc.h
+++ b/misc.h
@@ -252,6 +252,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);
 
 /*
@@ -265,10 +285,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 9ddc90027a92210fd92db27a69fe5a91c9627199..0db826a4be3d53e799eba2589307cace77e60c80 100644 (file)
--- 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 a1268ac2a9291dc1512fa28e3ab4efc65085c952..dffe882ad523ad40a62da7a9761dd513c3ed83f0 100644 (file)
--- a/ssl.c
+++ b/ssl.c
@@ -286,6 +286,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)
 {
@@ -294,6 +298,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
@@ -321,8 +327,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 c6a5627f0296b02497278795a268e5b49f88fb01..4373a800ebbe40ebed7d4acb4469791cfba12567 100644 (file)
--- a/ssl.h
+++ b/ssl.h
@@ -705,6 +705,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);
index 15445fc4da7e5f2e94e43798644d8a93536265c4..bad5ce0e939bf5972dc563c6b05c2e8e353d356b 100644 (file)
--- a/syshead.h
+++ b/syshead.h
@@ -660,6 +660,11 @@ socket_defined (const socket_descriptor_t sd)
 #define AUTO_USERID 0
 #endif
 
+/*
+ * Do we support challenge/response authentication, as a console-based client?
+ */
+#define ENABLE_CLIENT_CR
+
 /*
  * Do we support pushing peer info?
  */