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
115
as.c
115
as.c
|
@ -200,6 +200,75 @@ DownloadProfileContent(HANDLE hWnd, HINTERNET hRequest, char** pbuf, size_t* psi
|
||||||
return TRUE;
|
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
|
* 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) {
|
if (err == ERROR_INTERNET_SEC_CERT_REV_FAILED) {
|
||||||
DWORD flags;
|
DWORD flags;
|
||||||
DWORD len = sizeof(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;
|
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;
|
goto again;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -306,19 +375,47 @@ again:
|
||||||
}
|
}
|
||||||
|
|
||||||
/* get http status code */
|
/* get http status code */
|
||||||
DWORD statusCode = 0;
|
DWORD status_code = 0;
|
||||||
DWORD length = sizeof(DWORD);
|
DWORD length = sizeof(DWORD);
|
||||||
HttpQueryInfoW(hRequest, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &statusCode, &length, NULL);
|
HttpQueryInfoW(hRequest, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &status_code, &length, NULL);
|
||||||
if (statusCode != 200) {
|
|
||||||
ShowLocalizedMsgEx(MB_OK, hWnd, _T(PACKAGE_NAME), IDS_ERR_URL_IMPORT_PROFILE, statusCode, "HTTP error");
|
size_t size = 0;
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* download profile content */
|
/* download profile content */
|
||||||
size_t size = 0;
|
if ((status_code == 200) || (status_code == 401)) {
|
||||||
if (!DownloadProfileContent(hWnd, hRequest, &buf, &size))
|
if (!DownloadProfileContent(hWnd, hRequest, &buf, &size))
|
||||||
goto done;
|
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 name[MAX_PATH] = {0};
|
||||||
WCHAR* wbuf = Widen(buf);
|
WCHAR* wbuf = Widen(buf);
|
||||||
if (!wbuf) {
|
if (!wbuf) {
|
||||||
|
|
25
openvpn.c
25
openvpn.c
|
@ -64,24 +64,7 @@ TerminateOpenVPN(connection_t *c);
|
||||||
|
|
||||||
const TCHAR *cfgProp = _T("conn");
|
const TCHAR *cfgProp = _T("conn");
|
||||||
|
|
||||||
#define FLAG_CR_TYPE_SCRV1 0x1 /* static challenege */
|
void
|
||||||
#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
|
|
||||||
free_auth_param (auth_param_t *param)
|
free_auth_param (auth_param_t *param)
|
||||||
{
|
{
|
||||||
if (!param)
|
if (!param)
|
||||||
|
@ -89,6 +72,7 @@ free_auth_param (auth_param_t *param)
|
||||||
free (param->str);
|
free (param->str);
|
||||||
free (param->id);
|
free (param->id);
|
||||||
free (param->user);
|
free (param->user);
|
||||||
|
free (param->cr_response);
|
||||||
free (param);
|
free (param);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -929,7 +913,7 @@ free_dynamic_cr (connection_t *c)
|
||||||
* true on success. The caller must free param->str and param->id
|
* true on success. The caller must free param->str and param->id
|
||||||
* even on error.
|
* even on error.
|
||||||
*/
|
*/
|
||||||
static BOOL
|
BOOL
|
||||||
parse_dynamic_cr (const char *str, auth_param_t *param)
|
parse_dynamic_cr (const char *str, auth_param_t *param)
|
||||||
{
|
{
|
||||||
BOOL ret = FALSE;
|
BOOL ret = FALSE;
|
||||||
|
@ -1430,6 +1414,9 @@ WrapLine (WCHAR *line)
|
||||||
void
|
void
|
||||||
WriteStatusLog (connection_t *c, const WCHAR *prefix, const WCHAR *line, BOOL fileio)
|
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);
|
HWND logWnd = GetDlgItem(c->hwndStatus, ID_EDT_LOG);
|
||||||
FILE *log_fd;
|
FILE *log_fd;
|
||||||
time_t now;
|
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 */
|
/* 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);
|
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
|
#endif
|
||||||
|
|
Loading…
Reference in New Issue