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
	
	 Lev Stipakov
						Lev Stipakov