Added support for static challenge/response protocol.

This includes the new "static-challenge" directive.

See management/management-notes.txt for details on both
static and dynamic challenge/response protocols.

All client-side challenge/response code is #ifdefed on
ENABLE_CLIENT_CR and can be removed from the build
by commenting out the definition of ENABLE_CLIENT_CR
in syshead.h.

Version 2.1.3x.


git-svn-id: http://svn.openvpn.net/projects/openvpn/branches/BETA21/openvpn@7316 e7ae566f-a301-0410-adde-c780ea21d3b5
This commit is contained in:
James Yonan 2011-06-03 21:21:20 +00:00
parent a114cb750e
commit eab3e22f82
14 changed files with 274 additions and 68 deletions

View File

@ -42,6 +42,11 @@
static char base64_chars[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/*
* base64 encode input data of length size to malloced
* buffer which is returned as *str. Returns string
* length of *str.
*/
int
base64_encode(const void *data, int size, char **str)
{
@ -116,6 +121,11 @@ token_decode(const char *token)
return (marker << 24) | val;
}
/*
* Decode base64 str, outputting data to buffer
* at data of length size. Return length of
* decoded data written or -1 on error or overflow.
*/
int
base64_decode(const char *str, void *data, int size)
{

12
init.c
View File

@ -343,7 +343,13 @@ init_query_passwords (struct context *c)
#if P2MP
/* Auth user/pass input */
if (c->options.auth_user_pass_file)
auth_user_pass_setup (c->options.auth_user_pass_file);
{
#ifdef ENABLE_CLIENT_CR
auth_user_pass_setup (c->options.auth_user_pass_file, &c->options.sc_info);
#else
auth_user_pass_setup (c->options.auth_user_pass_file, NULL);
#endif
}
#endif
}
@ -2085,6 +2091,10 @@ do_init_crypto_tls (struct context *c, const unsigned int flags)
to.x509_track = options->x509_track;
#endif
#ifdef ENABLE_CLIENT_CR
to.sci = &options->sc_info;
#endif
/* TLS handshake authentication (--tls-auth) */
if (options->tls_auth_file)
{

View File

@ -606,25 +606,19 @@ man_up_finalize (struct management *man)
{
switch (man->connection.up_query_mode)
{
case UP_QUERY_DISABLED:
man->connection.up_query.defined = false;
break;
case UP_QUERY_USER_PASS:
if (strlen (man->connection.up_query.username) && strlen (man->connection.up_query.password))
man->connection.up_query.defined = true;
break;
if (!strlen (man->connection.up_query.username))
break;
/* fall through */
case UP_QUERY_PASS:
if (strlen (man->connection.up_query.password))
man->connection.up_query.defined = true;
break;
case UP_QUERY_NEED_OK:
if (strlen (man->connection.up_query.password))
man->connection.up_query.defined = true;
break;
case UP_QUERY_NEED_STR:
if (strlen (man->connection.up_query.password))
man->connection.up_query.defined = true;
break;
case UP_QUERY_DISABLED:
man->connection.up_query.defined = false;
break;
default:
ASSERT (0);
}
@ -665,16 +659,17 @@ man_query_user_pass (struct management *man,
static void
man_query_username (struct management *man, const char *type, const char *string)
{
const bool needed = (man->connection.up_query_mode == UP_QUERY_USER_PASS && man->connection.up_query_type);
const bool needed = ((man->connection.up_query_mode == UP_QUERY_USER_PASS
) && man->connection.up_query_type);
man_query_user_pass (man, type, string, needed, "username", man->connection.up_query.username, USER_PASS_LEN);
}
static void
man_query_password (struct management *man, const char *type, const char *string)
{
const bool needed = ((man->connection.up_query_mode == UP_QUERY_USER_PASS
|| man->connection.up_query_mode == UP_QUERY_PASS)
&& man->connection.up_query_type);
const bool needed = ((man->connection.up_query_mode == UP_QUERY_PASS
|| man->connection.up_query_mode == UP_QUERY_USER_PASS
) && man->connection.up_query_type);
if (!string[0]) /* allow blank passwords to be passed through using the blank_up tag */
string = blank_up;
man_query_user_pass (man, type, string, needed, "password", man->connection.up_query.password, USER_PASS_LEN);
@ -2843,7 +2838,8 @@ bool
management_query_user_pass (struct management *man,
struct user_pass *up,
const char *type,
const unsigned int flags)
const unsigned int flags,
const char *static_challenge)
{
struct gc_arena gc = gc_new ();
bool ret = false;
@ -2856,7 +2852,9 @@ management_query_user_pass (struct management *man,
const char *alert_type = NULL;
const char *prefix = NULL;
unsigned int up_query_mode = 0;
#ifdef ENABLE_CLIENT_CR
const char *sc = NULL;
#endif
ret = true;
man->persist.standalone_disabled = false; /* This is so M_CLIENT messages will be correctly passed through msg() */
man->persist.special_state_msg = NULL;
@ -2886,6 +2884,10 @@ management_query_user_pass (struct management *man,
up_query_mode = UP_QUERY_USER_PASS;
prefix = "PASSWORD";
alert_type = "username/password";
#ifdef ENABLE_CLIENT_CR
if (static_challenge)
sc = static_challenge;
#endif
}
buf_printf (&alert_msg, ">%s:Need '%s' %s",
prefix,
@ -2895,6 +2897,13 @@ management_query_user_pass (struct management *man,
if (flags & (GET_USER_PASS_NEED_OK | GET_USER_PASS_NEED_STR))
buf_printf (&alert_msg, " MSG:%s", up->username);
#ifdef ENABLE_CLIENT_CR
if (sc)
buf_printf (&alert_msg, " SC:%d,%s",
BOOL_CAST(flags & GET_USER_PASS_STATIC_CHALLENGE_ECHO),
sc);
#endif
man_wait_for_client_connection (man, &signal_received, 0, MWCC_PASSWORD_WAIT);
if (signal_received)
ret = false;
@ -2908,7 +2917,7 @@ management_query_user_pass (struct management *man,
man->connection.up_query_mode = up_query_mode;
man->connection.up_query_type = type;
/* run command processing event loop until we get our username/password */
/* run command processing event loop until we get our username/password/response */
do
{
man_standalone_event_loop (man, &signal_received, 0);

View File

@ -365,7 +365,11 @@ void management_set_callback (struct management *man,
void management_clear_callback (struct management *man);
bool management_query_user_pass (struct management *man, struct user_pass *up, const char *type, const unsigned int flags);
bool management_query_user_pass (struct management *man,
struct user_pass *up,
const char *type,
const unsigned int flags,
const char *static_challenge);
bool management_should_daemonize (struct management *man);
bool management_would_hold (struct management *man);

View File

@ -836,3 +836,113 @@ mappings, when not in single quotations:
interpret it as enclosing a parameter.
\[SPACE] Pass a literal space or tab character, don't
interpret it as a parameter delimiter.
Challenge/Response Protocol
---------------------------
The OpenVPN Challenge/Response Protocol allows an OpenVPN server to
generate challenge questions that are shown to the user, and to see
the user's responses to those challenges. Based on the responses, the
server can allow or deny access.
In this way, the OpenVPN Challenge/Response Protocol can be used
to implement multi-factor authentication. Two different
variations on the challenge/response protocol are supported: the
"Dynamic" and "Static" protocols.
The basic idea of Challenge/Response is that the user must enter an
additional piece of information, in addition to the username and
password, to successfully authenticate. Normally, this information
is used to prove that the user posesses a certain key-like device
such as cryptographic token or a particular mobile phone.
Dynamic protocol:
The OpenVPN dynamic challenge/response protocol works by returning
a specially formatted error message after initial successful
authentication. This error message contains the challenge question,
and 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
Static protocol:
The static protocol differs from the dynamic protocol in that the
challenge question and response field is given to the user in the
initial username/password dialog, and the username, password, and
response are delivered back to the server in a single transaction.
The "static-challenge" directive is used to give the challenge text
to OpenVPN and indicate whether or not the response should be echoed.
When the "static-challenge" directive is used, the management
interface will respond as such when credentials are needed:
>PASSWORD:Need 'Auth' username/password SC:<ECHO>,<TEXT>
ECHO: "1" if response should be echoed, "0" to not echo
TEXT: challenge text that should be shown to the user to
facilitate their response
For example:
>PASSWORD:Need 'Auth' username/password SC:1,Please enter token PIN
The above notification indicates that OpenVPN needs a --auth-user-pass
password plus a response to a static challenge ("Please enter token PIN").
The "1" after the "SC:" indicates that the response should be echoed.
The management interface client in this case should add the static
challenge text to the auth dialog followed by a field for the user to
enter a response. Then the client should pack the password and response
together into an encoded password:
username "Auth" foo
password "Auth" "SCRV1:<BASE64_PASSWORD>:<BASE64_RESPONSE>"
For example, if the user entered "bar" as the password and 8675309
as the PIN, the following management interface commands should be
issued:
username "Auth" foo
password "Auth" "SCRV1:Zm9v:ODY3NTMwOQ=="
Client-side support for challenge/response protocol:
Currently, the Access Server client and standalone OpenVPN
client support both static and dynamic challenge/response
protocols. However, any OpenVPN client UI that drives OpenVPN
via the management interface needs to add explicit support
for the challenge/response protocol.

72
misc.c
View File

@ -1387,10 +1387,16 @@ get_user_pass_cr (struct user_pass *up,
&& ((auth_file && streq (auth_file, "management")) || (from_stdin && (flags & GET_USER_PASS_MANAGEMENT)))
&& management_query_user_pass_enabled (management))
{
const char *sc = NULL;
if (flags & GET_USER_PASS_PREVIOUS_CREDS_FAILED)
management_auth_failure (management, prefix, "previous auth credentials failed");
if (!management_query_user_pass (management, up, prefix, flags))
#ifdef ENABLE_CLIENT_CR
if (auth_challenge && (flags & GET_USER_PASS_STATIC_CHALLENGE))
sc = auth_challenge;
#endif
if (!management_query_user_pass (management, up, prefix, flags, sc))
{
if ((flags & GET_USER_PASS_NOFATAL) != 0)
return false;
@ -1422,7 +1428,7 @@ get_user_pass_cr (struct user_pass *up,
else if (from_stdin)
{
#ifdef ENABLE_CLIENT_CR
if (auth_challenge)
if (auth_challenge && (flags & GET_USER_PASS_DYNAMIC_CHALLENGE))
{
struct auth_challenge_info *ac = get_auth_challenge (auth_challenge, &gc);
if (ac)
@ -1431,7 +1437,7 @@ get_user_pass_cr (struct user_pass *up,
struct buffer packed_resp;
buf_set_write (&packed_resp, (uint8_t*)up->password, USER_PASS_LEN);
msg (M_INFO, "CHALLENGE: %s", ac->challenge_text);
msg (M_INFO|M_NOPREFIX, "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);
@ -1461,6 +1467,28 @@ get_user_pass_cr (struct user_pass *up,
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);
#ifdef ENABLE_CLIENT_CR
if (auth_challenge && (flags & GET_USER_PASS_STATIC_CHALLENGE))
{
char *response = (char *) gc_malloc (USER_PASS_LEN, false, &gc);
struct buffer packed_resp;
char *pw64=NULL, *resp64=NULL;
msg (M_INFO|M_NOPREFIX, "CHALLENGE: %s", auth_challenge);
if (!get_console_input ("Response:", BOOL_CAST(flags & GET_USER_PASS_STATIC_CHALLENGE_ECHO), response, USER_PASS_LEN))
msg (M_FATAL, "ERROR: could not read static challenge response from stdin");
if (base64_encode(up->password, strlen(up->password), &pw64) == -1
|| base64_encode(response, strlen(response), &resp64) == -1)
msg (M_FATAL, "ERROR: could not base64-encode password/static_response");
buf_set_write (&packed_resp, (uint8_t*)up->password, USER_PASS_LEN);
buf_printf (&packed_resp, "SCRV1:%s:%s", pw64, resp64);
string_clear(pw64);
free(pw64);
string_clear(resp64);
free(resp64);
}
#endif
}
}
else
@ -1528,42 +1556,8 @@ get_user_pass_cr (struct user_pass *up,
#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
* See management/management-notes.txt for more info on the
* the dynamic challenge/response protocol implemented here.
*/
struct auth_challenge_info *
get_auth_challenge (const char *auth_challenge, struct gc_arena *gc)

15
misc.h
View File

@ -268,8 +268,19 @@ struct auth_challenge_info {
struct auth_challenge_info *get_auth_challenge (const char *auth_challenge, struct gc_arena *gc);
/*
* Challenge response info on client as pushed by server.
*/
struct static_challenge_info {
# define SC_ECHO (1<<0) /* echo response when typed by user */
unsigned int flags;
const char *challenge_text;
};
#else
struct auth_challenge_info {};
struct static_challenge_info {};
#endif
bool get_console_input (const char *prompt, const bool echo, char *input, const int capacity);
@ -285,6 +296,10 @@ 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)
#define GET_USER_PASS_DYNAMIC_CHALLENGE (1<<7) /* CRV1 protocol -- dynamic challenge */
#define GET_USER_PASS_STATIC_CHALLENGE (1<<8) /* SCRV1 protocol -- static challenge */
#define GET_USER_PASS_STATIC_CHALLENGE_ECHO (1<<9) /* SCRV1 protocol -- echo response */
bool get_user_pass_cr (struct user_pass *up,
const char *auth_file,
const char *prefix,

View File

@ -3362,6 +3362,21 @@ Note that while this option cannot be pushed, it can be controlled
from the management interface.
.\"*********************************************************
.TP
.B \-\-static\-challenge t e
Enable static challenge/response protocol using challenge text
.B t,
with
echo flag given by
.B e
(0|1).
The echo flag indicates whether or not the user's response
to the challenge should be echoed.
See management\-notes.txt in the OpenVPN distribution for a
description of the OpenVPN challenge/response protocol.
.\"*********************************************************
.TP
.B --server-poll-timeout n
when polling possible remote servers to connect to
in a round-robin fashion, spend no more than

View File

@ -443,6 +443,8 @@ static const char usage_message[] =
" when connecting to a '--mode server' remote host.\n"
"--auth-retry t : How to handle auth failures. Set t to\n"
" none (default), interact, or nointeract.\n"
"--static-challenge t e : Enable static challenge/response protocol using\n"
" challenge text t, with e indicating echo flag (0|1)\n"
"--server-poll-timeout n : when polling possible remote servers to connect to\n"
" in a round-robin fashion, spend no more than n seconds\n"
" waiting for a response before trying the next server.\n"
@ -5251,6 +5253,14 @@ add_option (struct options *options,
VERIFY_PERMISSION (OPT_P_GENERAL);
auth_retry_set (msglevel, p[1]);
}
#ifdef ENABLE_CLIENT_CR
else if (streq (p[0], "static-challenge") && p[1] && p[2])
{
options->sc_info.challenge_text = p[1];
if (atoi(p[2]))
options->sc_info.flags |= SC_ECHO;
}
#endif
#endif
#ifdef WIN32
else if (streq (p[0], "win-sys") && p[1])

View File

@ -426,6 +426,9 @@ struct options
const char *auth_user_pass_verify_script;
bool auth_user_pass_verify_script_via_file;
#ifdef ENABLE_CLIENT_CR
struct static_challenge_info sc_info;
#endif
#if PORT_SHARE
char *port_share_host;
int port_share_port;

32
ssl.c
View File

@ -292,17 +292,35 @@ static char *auth_challenge; /* GLOBAL */
#endif
void
auth_user_pass_setup (const char *auth_file)
auth_user_pass_setup (const char *auth_file, const struct static_challenge_info *sci)
{
auth_user_pass_enabled = true;
if (!auth_user_pass.defined)
{
#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);
# ifdef ENABLE_CLIENT_CR
if (auth_challenge) /* dynamic challenge/response */
get_user_pass_cr (&auth_user_pass,
auth_file,
UP_TYPE_AUTH,
GET_USER_PASS_MANAGEMENT|GET_USER_PASS_SENSITIVE|GET_USER_PASS_DYNAMIC_CHALLENGE,
auth_challenge);
else if (sci) /* static challenge response */
{
int flags = GET_USER_PASS_MANAGEMENT|GET_USER_PASS_SENSITIVE|GET_USER_PASS_STATIC_CHALLENGE;
if (sci->flags & SC_ECHO)
flags |= GET_USER_PASS_STATIC_CHALLENGE_ECHO;
get_user_pass_cr (&auth_user_pass,
auth_file,
UP_TYPE_AUTH,
flags,
sci->challenge_text);
}
else
# endif
get_user_pass (&auth_user_pass, auth_file, UP_TYPE_AUTH, GET_USER_PASS_MANAGEMENT|GET_USER_PASS_SENSITIVE);
#endif
}
}
@ -3945,7 +3963,11 @@ key_method_2_write (struct buffer *buf, struct tls_session *session)
/* write username/password if specified */
if (auth_user_pass_enabled)
{
auth_user_pass_setup (NULL);
#ifdef ENABLE_CLIENT_CR
auth_user_pass_setup (NULL, session->opt->sci);
#else
auth_user_pass_setup (NULL, NULL);
#endif
if (!write_string (buf, auth_user_pass.username, -1))
goto error;
if (!write_string (buf, auth_user_pass.password, -1))

6
ssl.h
View File

@ -516,6 +516,10 @@ struct tls_options
const struct x509_track *x509_track;
#endif
#ifdef ENABLE_CLIENT_CR
const struct static_challenge_info *sci;
#endif
/* --gremlin bits */
int gremlin;
};
@ -723,7 +727,7 @@ void get_highest_preference_tls_cipher (char *buf, int size);
void pem_password_setup (const char *auth_file);
int pem_password_callback (char *buf, int size, int rwflag, void *u);
void auth_user_pass_setup (const char *auth_file);
void auth_user_pass_setup (const char *auth_file, const struct static_challenge_info *sc_info);
void ssl_set_auth_nocache (void);
void ssl_set_auth_token (const char *token);
void ssl_purge_auth (const bool auth_user_pass_only);

View File

@ -683,7 +683,7 @@ socket_defined (const socket_descriptor_t sd)
#endif
/*
* Do we support challenge/response authentication, as a console-based client?
* Do we support challenge/response authentication as client?
*/
#define ENABLE_CLIENT_CR

View File

@ -1,5 +1,5 @@
dnl define the OpenVPN version
define(PRODUCT_VERSION,[2.1.3w])
define(PRODUCT_VERSION,[2.1.3x])
dnl define the TAP version
define(PRODUCT_TAP_ID,[tap0901])
define(PRODUCT_TAP_WIN32_MIN_MAJOR,[9])