Support for crtext

This adds support for crtext method of pending authentication,
used by Access Server 2.7 and newer.

When enabled on the server side and on the client side (IV_SSO=crtext),
server returns AUTH_PENDING with Info command like:

    CR_TEXT:R,E:Enter Authenticator Code

Client prompts user for the response and sends base64-encoded response
to the server via management interface command:

    cr-response SGFsbG8gV2VsdCE=

See https://github.com/OpenVPN/openvpn/blob/master/doc/management-notes.txt (crtext part)
for more information.

Signed-off-by: Lev Stipakov <lev@openvpn.net>
pull/431/head
Lev Stipakov 2021-06-01 15:11:31 +03:00 committed by Selva Nair
parent bb00d95f86
commit 5dcc584a7a
3 changed files with 125 additions and 14 deletions

44
misc.c
View File

@ -217,7 +217,7 @@ out:
* Generate a management command from double user inputs and send it
*/
BOOL
ManagementCommandFromInputBase64(connection_t *c, LPCSTR fmt, HWND hDlg,int id, int id2)
ManagementCommandFromTwoInputsBase64(connection_t *c, LPCSTR fmt, HWND hDlg,int id, int id2)
{
BOOL retval = FALSE;
LPSTR input, input2, input_b64, input2_b64, cmd;
@ -268,6 +268,48 @@ out:
return retval;
}
/*
* Generate a management command from base64-encoded user inputs and send it
*/
BOOL
ManagementCommandFromInputBase64(connection_t* c, LPCSTR fmt, HWND hDlg, int id)
{
BOOL retval = FALSE;
LPSTR input, input_b64, cmd;
int input_len, cmd_len;
input_b64 = NULL;
GetDlgItemTextUtf8(hDlg, id, &input, &input_len);
if (!Base64Encode(input, input_len, &input_b64))
goto out;
cmd_len = strlen(input_b64) + strlen(fmt);
cmd = malloc(cmd_len);
if (cmd)
{
snprintf(cmd, cmd_len, fmt, input_b64);
retval = ManagementCommand(c, cmd, NULL, regular);
free(cmd);
}
out:
/* Clear buffers with potentially secret content */
if (input_b64)
memset(input_b64, 0, strlen(input_b64));
free(input_b64);
if (input_len)
{
memset(input, 'x', input_len);
SetDlgItemTextA(hDlg, id, input);
free(input);
}
return retval;
}
/*
* Ensures the given directory exists, by checking for and

3
misc.h
View File

@ -25,7 +25,8 @@
#include <wincrypt.h>
BOOL ManagementCommandFromInput(connection_t *, LPCSTR, HWND, int);
BOOL ManagementCommandFromInputBase64(connection_t *, LPCSTR, HWND, int, int);
BOOL ManagementCommandFromTwoInputsBase64(connection_t*, LPCSTR, HWND, int, int);
BOOL ManagementCommandFromInputBase64(connection_t *, LPCSTR, HWND, int);
BOOL EnsureDirExists(LPTSTR);

View File

@ -64,13 +64,14 @@ TerminateOpenVPN(connection_t *c);
const TCHAR *cfgProp = _T("conn");
#define FLAG_CR_TYPE_SCRV1 0x1 /* static challenege */
#define FLAG_CR_TYPE_CRV1 0x2 /* dynamic challenege */
#define FLAG_CR_ECHO 0x4 /* echo the response */
#define FLAG_CR_RESPONSE 0x8 /* response needed */
#define FLAG_PASS_TOKEN 0x10 /* PKCS11 token password needed */
#define FLAG_STRING_PKCS11 0x20 /* PKCS11 id needed */
#define FLAG_PASS_PKEY 0x40 /* Private key password needed */
#define FLAG_CR_TYPE_SCRV1 0x1 /* static challenege */
#define FLAG_CR_TYPE_CRV1 0x2 /* dynamic challenege */
#define FLAG_CR_ECHO 0x4 /* echo the response */
#define FLAG_CR_RESPONSE 0x8 /* response needed */
#define FLAG_PASS_TOKEN 0x10 /* PKCS11 token password needed */
#define FLAG_STRING_PKCS11 0x20 /* PKCS11 id needed */
#define FLAG_PASS_PKEY 0x40 /* Private key password needed */
#define FLAG_CR_TYPE_CRTEXT 0x80 /* crtext */
typedef struct {
connection_t *c;
@ -572,7 +573,7 @@ UserAuthDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
}
ManagementCommandFromInput(param->c, "username \"Auth\" \"%s\"", hwndDlg, ID_EDT_AUTH_USER);
if (param->flags & FLAG_CR_TYPE_SCRV1)
ManagementCommandFromInputBase64(param->c, "password \"Auth\" \"SCRV1:%s:%s\"", hwndDlg, ID_EDT_AUTH_PASS, ID_EDT_AUTH_CHALLENGE);
ManagementCommandFromTwoInputsBase64(param->c, "password \"Auth\" \"SCRV1:%s:%s\"", hwndDlg, ID_EDT_AUTH_PASS, ID_EDT_AUTH_CHALLENGE);
else
ManagementCommandFromInput(param->c, "password \"Auth\" \"%s\"", hwndDlg, ID_EDT_AUTH_PASS);
EndDialog(hwndDlg, LOWORD(wParam));
@ -638,7 +639,7 @@ GenericPassDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
EndDialog(hwndDlg, LOWORD(wParam));
break;
}
if (param->flags & FLAG_CR_TYPE_CRV1)
if (param->flags & FLAG_CR_TYPE_CRV1 || param->flags & FLAG_CR_TYPE_CRTEXT)
{
SetDlgItemTextW(hwndDlg, ID_TXT_DESCRIPTION, wstr);
@ -705,6 +706,12 @@ GenericPassDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
SecureZeroMemory(password, sizeof(password));
return 0;
}
if (param->flags & FLAG_CR_TYPE_CRTEXT)
{
ManagementCommandFromInputBase64(param->c, "cr-response %s", hwndDlg, ID_EDT_RESPONSE);
EndDialog(hwndDlg, LOWORD(wParam));
return TRUE;
}
if (param->flags & FLAG_CR_TYPE_CRV1)
{
/* send username */
@ -938,6 +945,47 @@ out:
return ret;
}
/*
* Parse crtext string received from the server. Returns
* true on success. The caller must free param->str even on error.
*/
static BOOL
parse_crtext (const char* str, auth_param_t* param)
{
BOOL ret = FALSE;
char* token[2] = { 0 };
char* p = strdup(str);
int i;
char* p1;
if (!param || !p) goto out;
/* expected: str = "E,R:challenge_str" */
for (i = 0, p1 = p; i < 2; ++i, p1 = NULL)
{
token[i] = strtok(p1, ":"); /* strtok is thread-safe on Windows */
if (!token[i])
{
WriteStatusLog(param->c, L"GUI> ", L"Error parsing crtext string", false);
goto out;
}
}
param->flags |= FLAG_CR_TYPE_CRTEXT;
param->flags |= strchr(token[0], 'E') ? FLAG_CR_ECHO : 0;
param->flags |= strchr(token[0], 'R') ? FLAG_CR_RESPONSE : 0;
param->str = strdup(token[1]);
if (!param->str)
goto out;
ret = TRUE;
out:
free(p);
return ret;
}
/*
* Parse password or string request of the form "Need 'What' password/string MSG:message"
* and assign param->id = What, param->str = message. Also set param->flags if the type
@ -1282,8 +1330,9 @@ void OnByteCount(connection_t *c, char *msg)
}
/*
* Handle INFOMSG from OpenVPN. At the moment in only handles
* "OPEN_URL:<url>" message used by web-based extra authentication.
* Handle INFOMSG from OpenVPN. At the moment it handles
* OPEN_URL:<url> and CR_TEXT:<flags>:<challenge-str> messages
* used by two-step authentication.
*/
void OnInfoMsg(connection_t* c, char* msg)
{
@ -1298,6 +1347,25 @@ void OnInfoMsg(connection_t* c, char* msg)
}
free(url);
}
else if (strbegins(msg, "CR_TEXT:"))
{
auth_param_t* param = (auth_param_t*)calloc(1, sizeof(auth_param_t));
if (!param)
{
WriteStatusLog(c, L"GUI> ", L"Error: Out of memory - ignoring CR_TEXT request", false);
return;
}
param->c = c;
if (!parse_crtext(msg + 8, param))
{
WriteStatusLog(c, L"GUI> ", L"Error parsing crtext string", FALSE);
free_auth_param(param);
return;
}
LocalizedDialogBoxParam(ID_DLG_CHALLENGE_RESPONSE, GenericPassDialogFunc, (LPARAM)param);
}
}
/*
@ -2005,7 +2073,7 @@ StartOpenVPN(connection_t *c)
/* Construct command line -- put log first */
_sntprintf_0(cmdline, _T("openvpn --log%s \"%s\" --config \"%s\" "
"--setenv IV_GUI_VER \"%S\" --setenv IV_SSO openurl --service %s 0 --auth-retry interact "
"--setenv IV_GUI_VER \"%S\" --setenv IV_SSO openurl,crtext --service %s 0 --auth-retry interact "
"--management %S %hd stdin --management-query-passwords %s"
"--management-hold"),
(o.log_append ? _T("-append") : _T("")), c->log_path,