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
... | ... |
@@ -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 |