Browse code

Implement challenge/response authentication support in client mode, 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

James Yonan authored on 2010/10/24 18:12:47
Showing 7 changed files
... ...
@@ -33,7 +33,7 @@
33 33
 
34 34
 #include "syshead.h"
35 35
 
36
-#if defined(ENABLE_HTTP_PROXY) || defined(ENABLE_PKCS11)
36
+#if defined(ENABLE_HTTP_PROXY) || defined(ENABLE_PKCS11) || defined(ENABLE_CLIENT_CR)
37 37
 
38 38
 #include "base64.h"
39 39
 
... ...
@@ -26,6 +26,7 @@
26 26
 
27 27
 #include "buffer.h"
28 28
 #include "misc.h"
29
+#include "base64.h"
29 30
 #include "tun.h"
30 31
 #include "error.h"
31 32
 #include "thread.h"
... ...
@@ -1363,10 +1364,11 @@ get_console_input (const char *prompt, const bool echo, char *input, const int c
1363 1363
  */
1364 1364
 
1365 1365
 bool
1366
-get_user_pass (struct user_pass *up,
1367
-	       const char *auth_file,
1368
-	       const char *prefix,
1369
-	       const unsigned int flags)
1366
+get_user_pass_cr (struct user_pass *up,
1367
+		  const char *auth_file,
1368
+		  const char *prefix,
1369
+		  const unsigned int flags,
1370
+		  const char *auth_challenge)
1370 1371
 {
1371 1372
   struct gc_arena gc = gc_new ();
1372 1373
 
... ...
@@ -1379,7 +1381,7 @@ get_user_pass (struct user_pass *up,
1379 1379
 
1380 1380
 #ifdef ENABLE_MANAGEMENT
1381 1381
       /*
1382
-       * Get username/password from standard input?
1382
+       * Get username/password from management interface?
1383 1383
        */
1384 1384
       if (management
1385 1385
 	  && ((auth_file && streq (auth_file, "management")) || (from_stdin && (flags & GET_USER_PASS_MANAGEMENT)))
... ...
@@ -1419,22 +1421,47 @@ get_user_pass (struct user_pass *up,
1419 1419
        */
1420 1420
       else if (from_stdin)
1421 1421
 	{
1422
-	  struct buffer user_prompt = alloc_buf_gc (128, &gc);
1423
-	  struct buffer pass_prompt = alloc_buf_gc (128, &gc);
1424
-
1425
-	  buf_printf (&user_prompt, "Enter %s Username:", prefix);
1426
-	  buf_printf (&pass_prompt, "Enter %s Password:", prefix);
1427
-
1428
-	  if (!(flags & GET_USER_PASS_PASSWORD_ONLY))
1422
+#ifdef ENABLE_CLIENT_CR
1423
+	  if (auth_challenge)
1429 1424
 	    {
1430
-	      if (!get_console_input (BSTR (&user_prompt), true, up->username, USER_PASS_LEN))
1431
-		msg (M_FATAL, "ERROR: could not read %s username from stdin", prefix);
1432
-	      if (strlen (up->username) == 0)
1433
-		msg (M_FATAL, "ERROR: %s username is empty", prefix);
1425
+	      struct auth_challenge_info *ac = get_auth_challenge (auth_challenge, &gc);
1426
+	      if (ac)
1427
+		{
1428
+		  char *response = (char *) gc_malloc (USER_PASS_LEN, false, &gc);
1429
+		  struct buffer packed_resp;
1430
+
1431
+		  buf_set_write (&packed_resp, (uint8_t*)up->password, USER_PASS_LEN);
1432
+		  msg (M_INFO, "CHALLENGE: %s", ac->challenge_text);
1433
+		  if (!get_console_input ("Response:", BOOL_CAST(ac->flags&CR_ECHO), response, USER_PASS_LEN))
1434
+		    msg (M_FATAL, "ERROR: could not read challenge response from stdin");
1435
+		  strncpynt (up->username, ac->user, USER_PASS_LEN);
1436
+		  buf_printf (&packed_resp, "CRV1::%s::%s", ac->state_id, response);
1437
+		}
1438
+	      else
1439
+		{
1440
+		  msg (M_FATAL, "ERROR: received malformed challenge request from server");
1441
+		}
1434 1442
 	    }
1443
+	  else
1444
+#endif
1445
+	    {
1446
+	      struct buffer user_prompt = alloc_buf_gc (128, &gc);
1447
+	      struct buffer pass_prompt = alloc_buf_gc (128, &gc);
1435 1448
 
1436
-	  if (!get_console_input (BSTR (&pass_prompt), false, up->password, USER_PASS_LEN))
1437
-	    msg (M_FATAL, "ERROR: could not not read %s password from stdin", prefix);
1449
+	      buf_printf (&user_prompt, "Enter %s Username:", prefix);
1450
+	      buf_printf (&pass_prompt, "Enter %s Password:", prefix);
1451
+
1452
+	      if (!(flags & GET_USER_PASS_PASSWORD_ONLY))
1453
+		{
1454
+		  if (!get_console_input (BSTR (&user_prompt), true, up->username, USER_PASS_LEN))
1455
+		    msg (M_FATAL, "ERROR: could not read %s username from stdin", prefix);
1456
+		  if (strlen (up->username) == 0)
1457
+		    msg (M_FATAL, "ERROR: %s username is empty", prefix);
1458
+		}
1459
+
1460
+	      if (!get_console_input (BSTR (&pass_prompt), false, up->password, USER_PASS_LEN))
1461
+		msg (M_FATAL, "ERROR: could not not read %s password from stdin", prefix);
1462
+	    }
1438 1463
 	}
1439 1464
       else
1440 1465
 	{
... ...
@@ -1498,6 +1525,101 @@ get_user_pass (struct user_pass *up,
1498 1498
   return true;
1499 1499
 }
1500 1500
 
1501
+#ifdef ENABLE_CLIENT_CR
1502
+
1503
+/*
1504
+ * Parse a challenge message returned along with AUTH_FAILED.
1505
+ * The message is formatted as such:
1506
+ *
1507
+ *  CRV1:<flags>:<state_id>:<username_base64>:<challenge_text>
1508
+ *
1509
+ * flags: a series of optional, comma-separated flags:
1510
+ *  E : echo the response when the user types it
1511
+ *  R : a response is required
1512
+ *
1513
+ * state_id: an opaque string that should be returned to the server
1514
+ *  along with the response.
1515
+ *
1516
+ * username_base64 : the username formatted as base64
1517
+ *
1518
+ * challenge_text : the challenge text to be shown to the user
1519
+ *
1520
+ * Example challenge:
1521
+ *
1522
+ *   CRV1:R,E:Om01u7Fh4LrGBS7uh0SWmzwabUiGiW6l:Y3Ix:Please enter token PIN
1523
+ *
1524
+ * After showing the challenge_text and getting a response from the user
1525
+ * (if R flag is specified), the client should submit the following
1526
+ * auth creds back to the OpenVPN server:
1527
+ *
1528
+ * Username: [username decoded from username_base64]
1529
+ * Password: CRV1::<state_id>::<response_text>
1530
+ *
1531
+ * Where state_id is taken from the challenge request and response_text
1532
+ * is what the user entered in response to the challenge_text.
1533
+ * If the R flag is not present, response_text may be the empty
1534
+ * string.
1535
+ *
1536
+ * Example response (suppose the user enters "8675309" for the token PIN):
1537
+ *
1538
+ *   Username: cr1 ("Y3Ix" base64 decoded)
1539
+ *   Password: CRV1::Om01u7Fh4LrGBS7uh0SWmzwabUiGiW6l::8675309
1540
+ */
1541
+struct auth_challenge_info *
1542
+get_auth_challenge (const char *auth_challenge, struct gc_arena *gc)
1543
+{
1544
+  if (auth_challenge)
1545
+    {
1546
+      struct auth_challenge_info *ac;
1547
+      const int len = strlen (auth_challenge);
1548
+      char *work = (char *) gc_malloc (len+1, false, gc);
1549
+      char *cp;
1550
+
1551
+      struct buffer b;
1552
+      buf_set_read (&b, (const uint8_t *)auth_challenge, len);
1553
+
1554
+      ALLOC_OBJ_CLEAR_GC (ac, struct auth_challenge_info, gc);
1555
+
1556
+      /* parse prefix */
1557
+      if (!buf_parse(&b, ':', work, len))
1558
+	return NULL;
1559
+      if (strcmp(work, "CRV1"))
1560
+	return NULL;
1561
+
1562
+      /* parse flags */
1563
+      if (!buf_parse(&b, ':', work, len))
1564
+	return NULL;
1565
+      for (cp = work; *cp != '\0'; ++cp)
1566
+	{
1567
+	  const char c = *cp;
1568
+	  if (c == 'E')
1569
+	    ac->flags |= CR_ECHO;
1570
+	  else if (c == 'R')
1571
+	    ac->flags |= CR_RESPONSE;
1572
+	}
1573
+      
1574
+      /* parse state ID */
1575
+      if (!buf_parse(&b, ':', work, len))
1576
+	return NULL;
1577
+      ac->state_id = string_alloc(work, gc);
1578
+
1579
+      /* parse user name */
1580
+      if (!buf_parse(&b, ':', work, len))
1581
+	return NULL;
1582
+      ac->user = (char *) gc_malloc (strlen(work)+1, true, gc);
1583
+      base64_decode(work, (void*)ac->user);
1584
+
1585
+      /* parse challenge text */
1586
+      ac->challenge_text = string_alloc(BSTR(&b), gc);
1587
+
1588
+      return ac;
1589
+    }
1590
+  else
1591
+    return NULL;
1592
+}
1593
+
1594
+#endif
1595
+
1501 1596
 #if AUTO_USERID
1502 1597
 
1503 1598
 static const char *
... ...
@@ -252,6 +252,26 @@ struct user_pass
252 252
   char password[USER_PASS_LEN];
253 253
 };
254 254
 
255
+#ifdef ENABLE_CLIENT_CR
256
+/*
257
+ * Challenge response info on client as pushed by server.
258
+ */
259
+struct auth_challenge_info {
260
+# define CR_ECHO     (1<<0) /* echo response when typed by user */
261
+# define CR_RESPONSE (1<<1) /* response needed */
262
+  unsigned int flags;
263
+
264
+  const char *user;
265
+  const char *state_id;
266
+  const char *challenge_text;
267
+};
268
+
269
+struct auth_challenge_info *get_auth_challenge (const char *auth_challenge, struct gc_arena *gc);
270
+
271
+#else
272
+struct auth_challenge_info {};
273
+#endif
274
+
255 275
 bool get_console_input (const char *prompt, const bool echo, char *input, const int capacity);
256 276
 
257 277
 /*
... ...
@@ -265,10 +285,20 @@ bool get_console_input (const char *prompt, const bool echo, char *input, const
265 265
 #define GET_USER_PASS_NEED_STR      (1<<5)
266 266
 #define GET_USER_PASS_PREVIOUS_CREDS_FAILED (1<<6)
267 267
 
268
-bool get_user_pass (struct user_pass *up,
269
-		    const char *auth_file,
270
-		    const char *prefix,
271
-		    const unsigned int flags);
268
+bool get_user_pass_cr (struct user_pass *up,
269
+		       const char *auth_file,
270
+		       const char *prefix,
271
+		       const unsigned int flags,
272
+		       const char *auth_challenge);
273
+
274
+static inline bool
275
+get_user_pass (struct user_pass *up,
276
+	       const char *auth_file,
277
+	       const char *prefix,
278
+	       const unsigned int flags)
279
+{
280
+  return get_user_pass_cr (up, auth_file, prefix, flags, NULL);
281
+}
272 282
 
273 283
 void fail_user_pass (const char *prefix,
274 284
 		     const unsigned int flags,
... ...
@@ -68,8 +68,18 @@ receive_auth_failed (struct context *c, const struct buffer *buffer)
68 68
 	  if (buf_string_compare_advance (&buf, "AUTH_FAILED,") && BLEN (&buf))
69 69
 	    reason = BSTR (&buf);
70 70
 	  management_auth_failure (management, UP_TYPE_AUTH, reason);
71
-	}
71
+	} else
72 72
 #endif
73
+	{
74
+#ifdef ENABLE_CLIENT_CR
75
+	  struct buffer buf = *buffer;
76
+	  if (buf_string_match_head_str (&buf, "AUTH_FAILED,CRV1:") && BLEN (&buf))
77
+	    {
78
+	      buf_advance (&buf, 12); /* Length of "AUTH_FAILED," substring */
79
+	      ssl_put_auth_challenge (BSTR (&buf));
80
+	    }
81
+#endif
82
+	}
73 83
     }
74 84
 }
75 85
 
... ...
@@ -286,6 +286,10 @@ pem_password_callback (char *buf, int size, int rwflag, void *u)
286 286
 static bool auth_user_pass_enabled;     /* GLOBAL */
287 287
 static struct user_pass auth_user_pass; /* GLOBAL */
288 288
 
289
+#ifdef ENABLE_CLIENT_CR
290
+static char *auth_challenge; /* GLOBAL */
291
+#endif
292
+
289 293
 void
290 294
 auth_user_pass_setup (const char *auth_file)
291 295
 {
... ...
@@ -294,6 +298,8 @@ auth_user_pass_setup (const char *auth_file)
294 294
     {
295 295
 #if AUTO_USERID
296 296
       get_user_pass_auto_userid (&auth_user_pass, auth_file);
297
+#elif defined(ENABLE_CLIENT_CR)
298
+      get_user_pass_cr (&auth_user_pass, auth_file, UP_TYPE_AUTH, GET_USER_PASS_MANAGEMENT|GET_USER_PASS_SENSITIVE, auth_challenge);
297 299
 #else
298 300
       get_user_pass (&auth_user_pass, auth_file, UP_TYPE_AUTH, GET_USER_PASS_MANAGEMENT|GET_USER_PASS_SENSITIVE);
299 301
 #endif
... ...
@@ -321,8 +327,29 @@ ssl_purge_auth (void)
321 321
 #endif
322 322
   purge_user_pass (&passbuf, true);
323 323
   purge_user_pass (&auth_user_pass, true);
324
+#ifdef ENABLE_CLIENT_CR
325
+  ssl_purge_auth_challenge();
326
+#endif
327
+}
328
+
329
+#ifdef ENABLE_CLIENT_CR
330
+
331
+void
332
+ssl_purge_auth_challenge (void)
333
+{
334
+  free (auth_challenge);
335
+  auth_challenge = NULL;
324 336
 }
325 337
 
338
+void
339
+ssl_put_auth_challenge (const char *cr_str)
340
+{
341
+  ssl_purge_auth_challenge();
342
+  auth_challenge = string_alloc(cr_str, NULL);
343
+}
344
+
345
+#endif
346
+
326 347
 /*
327 348
  * OpenSSL callback to get a temporary RSA key, mostly
328 349
  * used for export ciphers.
... ...
@@ -705,6 +705,17 @@ void auth_user_pass_setup (const char *auth_file);
705 705
 void ssl_set_auth_nocache (void);
706 706
 void ssl_purge_auth (void);
707 707
 
708
+
709
+#ifdef ENABLE_CLIENT_CR
710
+/*
711
+ * ssl_get_auth_challenge will parse the server-pushed auth-failed
712
+ * reason string and return a dynamically allocated
713
+ * auth_challenge_info struct.
714
+ */
715
+void ssl_purge_auth_challenge (void);
716
+void ssl_put_auth_challenge (const char *cr_str);
717
+#endif
718
+
708 719
 void tls_set_verify_command (const char *cmd);
709 720
 void tls_set_crl_verify (const char *crl);
710 721
 void tls_set_verify_x509name (const char *x509name);
... ...
@@ -661,6 +661,11 @@ socket_defined (const socket_descriptor_t sd)
661 661
 #endif
662 662
 
663 663
 /*
664
+ * Do we support challenge/response authentication, as a console-based client?
665
+ */
666
+#define ENABLE_CLIENT_CR
667
+
668
+/*
664 669
  * Do we support pushing peer info?
665 670
  */
666 671
 #define ENABLE_PUSH_PEER_INFO