mirror of https://github.com/OpenVPN/openvpn-gui
URL profile import: support for 2FA
When 2FA is enabled, server (such as AS) replies with HTTP 401 and issues a challenge. Use existing facilities to parse CRV message and prompt user for a response, then call REST method again with encoded response as HTTP auth password. See https://github.com/OpenVPN/openvpn3/blob/master/doc/webauth.md#challengeresponse-authentication for more information. Signed-off-by: Lev Stipakov <lev@openvpn.net>pull/446/head
parent
c7beb04ff5
commit
e3b06efcd2
117
as.c
117
as.c
|
@ -200,6 +200,75 @@ DownloadProfileContent(HANDLE hWnd, HINTERNET hRequest, char** pbuf, size_t* psi
|
|||
return TRUE;
|
||||
}
|
||||
|
||||
/*
|
||||
* DialogProc for challenge-response
|
||||
*/
|
||||
INT_PTR CALLBACK
|
||||
CRDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
auth_param_t * param = NULL;
|
||||
|
||||
switch (msg) {
|
||||
case WM_INITDIALOG:
|
||||
param = (auth_param_t*)lParam;
|
||||
SetProp(hwndDlg, cfgProp, (HANDLE)param);
|
||||
|
||||
WCHAR *wstr = Widen(param->str);
|
||||
if (!wstr) {
|
||||
EndDialog(hwndDlg, LOWORD(wParam));
|
||||
break;
|
||||
}
|
||||
SetDlgItemTextW(hwndDlg, ID_TXT_DESCRIPTION, wstr);
|
||||
free(wstr);
|
||||
|
||||
/* Set password echo on if needed */
|
||||
if (param->flags & FLAG_CR_ECHO)
|
||||
SendMessage(GetDlgItem(hwndDlg, ID_EDT_RESPONSE), EM_SETPASSWORDCHAR, 0, 0);
|
||||
|
||||
SetForegroundWindow(hwndDlg);
|
||||
|
||||
/* disable OK button by default - not disabled in resources */
|
||||
EnableWindow(GetDlgItem(hwndDlg, IDOK), FALSE);
|
||||
break;
|
||||
|
||||
case WM_COMMAND:
|
||||
param = (auth_param_t*)GetProp(hwndDlg, cfgProp);
|
||||
|
||||
switch (LOWORD(wParam)) {
|
||||
case ID_EDT_RESPONSE:
|
||||
if (HIWORD(wParam) == EN_UPDATE) {
|
||||
/* enable OK if response is non-empty */
|
||||
BOOL enableOK = GetWindowTextLength((HWND)lParam);
|
||||
EnableWindow(GetDlgItem(hwndDlg, IDOK), enableOK);
|
||||
}
|
||||
break;
|
||||
|
||||
case IDOK: {
|
||||
int len = 0;
|
||||
GetDlgItemTextUtf8(hwndDlg, ID_EDT_RESPONSE, ¶m->cr_response, &len);
|
||||
EndDialog(hwndDlg, LOWORD(wParam));
|
||||
}
|
||||
return TRUE;
|
||||
|
||||
case IDCANCEL:
|
||||
EndDialog(hwndDlg, LOWORD(wParam));
|
||||
return TRUE;
|
||||
}
|
||||
break;
|
||||
|
||||
case WM_CLOSE:
|
||||
EndDialog(hwndDlg, LOWORD(wParam));
|
||||
return TRUE;
|
||||
|
||||
case WM_NCDESTROY:
|
||||
param = (auth_param_t*)GetProp(hwndDlg, cfgProp);
|
||||
RemoveProp(hwndDlg, cfgProp);
|
||||
break;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download profile from AS and save it to a special-named temp file
|
||||
*
|
||||
|
@ -287,10 +356,10 @@ again:
|
|||
if (err == ERROR_INTERNET_SEC_CERT_REV_FAILED) {
|
||||
DWORD flags;
|
||||
DWORD len = sizeof(flags);
|
||||
InternetQueryOption(hRequest, INTERNET_OPTION_SECURITY_FLAGS, (LPVOID)&flags, &len);
|
||||
InternetQueryOptionW(hRequest, INTERNET_OPTION_SECURITY_FLAGS, (LPVOID)&flags, &len);
|
||||
|
||||
flags |= SECURITY_FLAG_IGNORE_REVOCATION;
|
||||
InternetSetOption(hRequest, INTERNET_OPTION_SECURITY_FLAGS, &flags, sizeof(flags));
|
||||
InternetSetOptionW(hRequest, INTERNET_OPTION_SECURITY_FLAGS, &flags, sizeof(flags));
|
||||
goto again;
|
||||
}
|
||||
|
||||
|
@ -306,18 +375,46 @@ again:
|
|||
}
|
||||
|
||||
/* get http status code */
|
||||
DWORD statusCode = 0;
|
||||
DWORD status_code = 0;
|
||||
DWORD length = sizeof(DWORD);
|
||||
HttpQueryInfoW(hRequest, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &statusCode, &length, NULL);
|
||||
if (statusCode != 200) {
|
||||
ShowLocalizedMsgEx(MB_OK, hWnd, _T(PACKAGE_NAME), IDS_ERR_URL_IMPORT_PROFILE, statusCode, "HTTP error");
|
||||
goto done;
|
||||
}
|
||||
HttpQueryInfoW(hRequest, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &status_code, &length, NULL);
|
||||
|
||||
size_t size = 0;
|
||||
|
||||
/* download profile content */
|
||||
size_t size = 0;
|
||||
if (!DownloadProfileContent(hWnd, hRequest, &buf, &size))
|
||||
if ((status_code == 200) || (status_code == 401)) {
|
||||
if (!DownloadProfileContent(hWnd, hRequest, &buf, &size))
|
||||
goto done;
|
||||
|
||||
char* msg_begin = strstr(buf, "<Message>CRV1:");
|
||||
char* msg_end = strstr(buf, "</Message>");
|
||||
if ((status_code == 401) && msg_begin && msg_end) {
|
||||
*msg_end = '\0';
|
||||
auth_param_t* param = (auth_param_t*)calloc(1, sizeof(auth_param_t));
|
||||
if (!param)
|
||||
goto done;
|
||||
|
||||
if (parse_dynamic_cr(msg_begin + 14, param)) {
|
||||
/* prompt user for dynamic challenge */
|
||||
INT_PTR res = LocalizedDialogBoxParam(ID_DLG_CHALLENGE_RESPONSE, CRDialogFunc, (LPARAM)param);
|
||||
if (res == IDOK)
|
||||
_snprintf_0(password, "CRV1::%s::%s", param->id, param->cr_response);
|
||||
|
||||
free_auth_param(param);
|
||||
|
||||
if (res == IDOK)
|
||||
goto again;
|
||||
}
|
||||
else {
|
||||
free_auth_param(param);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (status_code != 200) {
|
||||
ShowLocalizedMsgEx(MB_OK, hWnd, _T(PACKAGE_NAME), IDS_ERR_URL_IMPORT_PROFILE, status_code, L"HTTP error");
|
||||
goto done;
|
||||
}
|
||||
|
||||
WCHAR name[MAX_PATH] = {0};
|
||||
WCHAR* wbuf = Widen(buf);
|
||||
|
|
25
openvpn.c
25
openvpn.c
|
@ -64,24 +64,7 @@ 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_CRTEXT 0x80 /* crtext */
|
||||
|
||||
typedef struct {
|
||||
connection_t *c;
|
||||
unsigned int flags;
|
||||
char *str;
|
||||
char *id;
|
||||
char *user;
|
||||
} auth_param_t;
|
||||
|
||||
static void
|
||||
void
|
||||
free_auth_param (auth_param_t *param)
|
||||
{
|
||||
if (!param)
|
||||
|
@ -89,6 +72,7 @@ free_auth_param (auth_param_t *param)
|
|||
free (param->str);
|
||||
free (param->id);
|
||||
free (param->user);
|
||||
free (param->cr_response);
|
||||
free (param);
|
||||
}
|
||||
|
||||
|
@ -929,7 +913,7 @@ free_dynamic_cr (connection_t *c)
|
|||
* true on success. The caller must free param->str and param->id
|
||||
* even on error.
|
||||
*/
|
||||
static BOOL
|
||||
BOOL
|
||||
parse_dynamic_cr (const char *str, auth_param_t *param)
|
||||
{
|
||||
BOOL ret = FALSE;
|
||||
|
@ -1430,6 +1414,9 @@ WrapLine (WCHAR *line)
|
|||
void
|
||||
WriteStatusLog (connection_t *c, const WCHAR *prefix, const WCHAR *line, BOOL fileio)
|
||||
{
|
||||
/* this can be called without connection (AS profile import), so do nothing in this case */
|
||||
if (!c) return;
|
||||
|
||||
HWND logWnd = GetDlgItem(c->hwndStatus, ID_EDT_LOG);
|
||||
FILE *log_fd;
|
||||
time_t now;
|
||||
|
|
29
openvpn.h
29
openvpn.h
|
@ -57,4 +57,33 @@ extern const TCHAR *cfgProp;
|
|||
/* Write a line to status window and optionally to the log file */
|
||||
void WriteStatusLog (connection_t *c, const WCHAR *prefix, const WCHAR *line, BOOL fileio);
|
||||
|
||||
#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;
|
||||
unsigned int flags;
|
||||
char* str;
|
||||
char* id;
|
||||
char* user;
|
||||
char* cr_response;
|
||||
} auth_param_t;
|
||||
|
||||
/*
|
||||
* Parse dynamic challenge string received from the server. Returns
|
||||
* true on success. The caller must free param->str and param->id
|
||||
* even on error.
|
||||
*/
|
||||
BOOL
|
||||
parse_dynamic_cr(const char* str, auth_param_t* param);
|
||||
|
||||
void
|
||||
free_auth_param(auth_param_t* param);
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue