diff options
author | James Yonan <james@openvpn.net> | 2010-10-24 09:12:47 +0000 |
---|---|---|
committer | James Yonan <james@openvpn.net> | 2010-10-24 09:12:47 +0000 |
commit | 3cf9dd88fd84108eccfcce0ebf44e00f9481cd82 (patch) | |
tree | 03264d8f2741babc01a8ab2bbe9a8d2b546f3147 | |
parent | Fixed initialization bug in route_list_add_default_gateway (diff) | |
download | openvpn-3cf9dd88fd84108eccfcce0ebf44e00f9481cd82.tar.xz |
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
-rw-r--r-- | base64.c | 2 | ||||
-rw-r--r-- | misc.c | 158 | ||||
-rw-r--r-- | misc.h | 38 | ||||
-rw-r--r-- | push.c | 12 | ||||
-rw-r--r-- | ssl.c | 27 | ||||
-rw-r--r-- | ssl.h | 11 | ||||
-rw-r--r-- | syshead.h | 5 |
7 files changed, 229 insertions, 24 deletions
@@ -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" @@ -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 * @@ -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, @@ -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 + } } } @@ -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. @@ -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); @@ -661,6 +661,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 |