From 5dcc584a7a4800753ee1ed4cbcab476211e5d247 Mon Sep 17 00:00:00 2001 From: Lev Stipakov Date: Tue, 1 Jun 2021 15:11:31 +0300 Subject: [PATCH] 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 --- misc.c | 44 +++++++++++++++++++++++++- misc.h | 3 +- openvpn.c | 92 +++++++++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 125 insertions(+), 14 deletions(-) diff --git a/misc.c b/misc.c index 1a53b33..2c5c258 100644 --- a/misc.c +++ b/misc.c @@ -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 diff --git a/misc.h b/misc.h index e6464d8..7e102cd 100644 --- a/misc.h +++ b/misc.h @@ -25,7 +25,8 @@ #include 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); diff --git a/openvpn.c b/openvpn.c index e52b909..d47c9ba 100644 --- a/openvpn.c +++ b/openvpn.c @@ -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:" message used by web-based extra authentication. + * Handle INFOMSG from OpenVPN. At the moment it handles + * OPEN_URL: and CR_TEXT:: 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,