mirror of https://github.com/OpenVPN/openvpn-gui
Handle pkcs11-id query from daemon
Add support for selecting pkcs11-id from the GUI. Requires --management-pkcs11-id in the config file. This option is not added by the GUI. A list of all available pkcs11 certificates are presented to the user with buttons OK, Cancel, Retry. OK submits the selected entry, Cancel closes the connection, Retry reconstructs the list of certificates by querying the daemon again. The latter can be used to retry after inserting a token. If no certificates are found, a message suggesting to insert a token and press 'Retry' is displayed. The list shows the "Issued-to", "Issued-by" names (usually the subject & issuer common names) and valid-until date in current locale for each certificate. Signed-off-by: Selva Nair <selva.nair@gmail.com>pull/513/head
parent
05779fbb9b
commit
84be448777
|
@ -28,6 +28,7 @@ add_executable(${PROJECT_NAME} WIN32
|
|||
tray.c
|
||||
viewlog.c
|
||||
as.c
|
||||
pkcs11.c
|
||||
res/openvpn-gui-res.rc)
|
||||
|
||||
find_package(OpenSSL REQUIRED)
|
||||
|
@ -89,4 +90,4 @@ if(MSVC)
|
|||
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/$<0:>
|
||||
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/$<0:>
|
||||
ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/$<0:>)
|
||||
endif(MSVC)
|
||||
endif(MSVC)
|
||||
|
|
|
@ -95,6 +95,7 @@ openvpn_gui_SOURCES = \
|
|||
env_set.c env_set.h \
|
||||
echo.c echo.h \
|
||||
as.c as.h \
|
||||
pkcs11.c pkcs11.h \
|
||||
openvpn-gui-res.h
|
||||
|
||||
openvpn_gui_LDFLAGS = -mwindows
|
||||
|
|
11
manage.c
11
manage.c
|
@ -337,6 +337,17 @@ OnManagement(SOCKET sk, LPARAM lParam)
|
|||
if (rtmsg_handler[infomsg_])
|
||||
rtmsg_handler[infomsg_](c, pos + 8);
|
||||
}
|
||||
else if (strncmp(pos, "PKCS11ID", 8) == 0
|
||||
&& c->manage.cmd_queue)
|
||||
{
|
||||
/* This is not a real-time message, but unfortunately implemented
|
||||
* in the core as one. Work around by handling the response here.
|
||||
*/
|
||||
mgmt_cmd_t *cmd = c->manage.cmd_queue;
|
||||
if (cmd->handler)
|
||||
cmd->handler(c, line);
|
||||
UnqueueCommand(c);
|
||||
}
|
||||
}
|
||||
else if (c->manage.cmd_queue)
|
||||
{
|
||||
|
|
|
@ -153,6 +153,11 @@
|
|||
#define ID_CHK_AUTOLOGIN 402
|
||||
#define IDS_ERR_URL_IMPORT_PROFILE 403
|
||||
|
||||
/* pkcs11-id query dialog */
|
||||
#define ID_DLG_PKCS11_QUERY 450
|
||||
#define ID_LVW_PKCS11 451
|
||||
#define ID_TXT_PKCS11 452
|
||||
|
||||
/*
|
||||
* String Table Resources
|
||||
*/
|
||||
|
@ -360,6 +365,13 @@
|
|||
#define IDS_ERR_INVALID_PASSWORD_INPUT 2152
|
||||
#define IDS_ERR_INVALID_USERNAME_INPUT 2153
|
||||
|
||||
/* pkcs11 related */
|
||||
#define IDS_ERR_NO_PKCS11 2160
|
||||
#define IDS_ERR_SELECT_PKCS11 2161
|
||||
#define IDS_CERT_DISPLAYNAME 2162
|
||||
#define IDS_CERT_ISSUER 2163
|
||||
#define IDS_CERT_NOTAFTER 2164
|
||||
|
||||
/* Timer IDs */
|
||||
#define IDT_STOP_TIMER 2500 /* Timer used to trigger force termination */
|
||||
|
||||
|
|
|
@ -60,6 +60,7 @@
|
|||
#include "save_pass.h"
|
||||
#include "env_set.h"
|
||||
#include "echo.h"
|
||||
#include "pkcs11.h"
|
||||
|
||||
#define OPENVPN_SERVICE_PIPE_NAME_OVPN2 L"\\\\.\\pipe\\openvpn\\service"
|
||||
#define OPENVPN_SERVICE_PIPE_NAME_OVPN3 L"\\\\.\\pipe\\ovpnagent"
|
||||
|
@ -1758,6 +1759,12 @@ out:
|
|||
void
|
||||
OnNeedStr (connection_t *c, UNUSED char *msg)
|
||||
{
|
||||
if (strstr(msg, "Need 'pkcs11-id-request'"))
|
||||
{
|
||||
msg = strstr(msg, "MSG:");
|
||||
OnPkcs11(c, msg ? msg + 4 : "");
|
||||
return;
|
||||
}
|
||||
WriteStatusLog (c, L"GUI> ", L"Error: Received NEED-STR message -- not implemented", false);
|
||||
}
|
||||
|
||||
|
@ -1773,6 +1780,7 @@ Cleanup (connection_t *c)
|
|||
env_item_del_all(c->es);
|
||||
c->es = NULL;
|
||||
echo_msg_clear(c, true); /* clear history */
|
||||
pkcs11_list_clear(&c->pkcs11_list);
|
||||
|
||||
if (c->hProcess)
|
||||
CloseHandle (c->hProcess);
|
||||
|
|
|
@ -34,6 +34,7 @@ typedef struct connection connection_t;
|
|||
|
||||
#include "manage.h"
|
||||
#include "echo.h"
|
||||
#include "pkcs11.h"
|
||||
|
||||
#define MAX_NAME (UNLEN + 1)
|
||||
|
||||
|
@ -158,6 +159,7 @@ struct connection {
|
|||
unsigned long long int bytes_out;
|
||||
struct env_item *es; /* Pointer to the head of config-specific env variables list */
|
||||
struct echo_msg echo_msg; /* Message echo-ed from server or client config and related data */
|
||||
struct pkcs11_list pkcs11_list;
|
||||
};
|
||||
|
||||
/* All options used within OpenVPN GUI */
|
||||
|
|
|
@ -0,0 +1,634 @@
|
|||
/*
|
||||
* OpenVPN-GUI -- A Windows GUI for OpenVPN.
|
||||
*
|
||||
* Copyright (C) 2022 Selva Nair <selva.nair@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program (see the file COPYING included with this
|
||||
* distribution); if not, write to the Free Software Foundation, Inc.,
|
||||
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <windows.h>
|
||||
#include "pkcs11.h"
|
||||
#include "options.h"
|
||||
#include "main.h"
|
||||
#include "manage.h"
|
||||
#include "openvpn.h"
|
||||
#include "misc.h"
|
||||
#include "openvpn-gui-res.h"
|
||||
#include "localization.h"
|
||||
#include <commctrl.h>
|
||||
#include <shlwapi.h>
|
||||
#include <assert.h>
|
||||
|
||||
extern options_t o;
|
||||
|
||||
/* state of list array */
|
||||
#define STATE_GET_COUNT 1
|
||||
#define STATE_GET_ENTRY 2
|
||||
#define STATE_FILLED 4
|
||||
#define STATE_SELECTED 8
|
||||
|
||||
struct cert_info
|
||||
{
|
||||
wchar_t *commonname;
|
||||
wchar_t *issuer;
|
||||
wchar_t *notAfter;
|
||||
};
|
||||
|
||||
struct pkcs11_entry
|
||||
{
|
||||
char *id; /* pkcs11-id string value as received from daemon */
|
||||
struct cert_info cert; /* decoded certificate structure */
|
||||
};
|
||||
|
||||
static void
|
||||
certificate_free(struct cert_info *cert)
|
||||
{
|
||||
if (cert)
|
||||
{
|
||||
free(cert->commonname);
|
||||
free(cert->issuer);
|
||||
free(cert->notAfter);
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
pkcs11_list_alloc(struct pkcs11_list *l)
|
||||
{
|
||||
if (l->count > 0 && !l->pe)
|
||||
{
|
||||
l->pe = calloc(l->count, sizeof(struct pkcs11_entry));
|
||||
}
|
||||
return (l->pe != NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
pkcs11_entry_free(struct pkcs11_entry *pe)
|
||||
{
|
||||
if (!pe)
|
||||
{
|
||||
return;
|
||||
}
|
||||
free(pe->id);
|
||||
certificate_free(&pe->cert);
|
||||
}
|
||||
|
||||
/* Free any allocated memory and clear the list */
|
||||
void
|
||||
pkcs11_list_clear(struct pkcs11_list *l)
|
||||
{
|
||||
if (l->pe)
|
||||
{
|
||||
for (UINT i = 0; i < l->count; i++)
|
||||
{
|
||||
pkcs11_entry_free(&l->pe[i]);
|
||||
}
|
||||
free(l->pe);
|
||||
}
|
||||
|
||||
CLEAR(*l);
|
||||
}
|
||||
|
||||
/* Get the "friendly name" (usually the commonname) from a certificate
|
||||
* context. If issuer = true, the issuer name is parsed, else the
|
||||
* subject. A newly allocated wide char string is returned.
|
||||
*/
|
||||
static wchar_t *
|
||||
extract_name_entry(const CERT_CONTEXT *ctx, bool issuer)
|
||||
{
|
||||
DWORD size = CertGetNameStringW(ctx, CERT_NAME_FRIENDLY_DISPLAY_TYPE,
|
||||
issuer ? CERT_NAME_ISSUER_FLAG : 0, NULL, NULL, 0);
|
||||
|
||||
wchar_t *name = malloc(size*sizeof(wchar_t));
|
||||
if (name)
|
||||
{
|
||||
size = CertGetNameStringW(ctx, CERT_NAME_FRIENDLY_DISPLAY_TYPE,
|
||||
issuer ? CERT_NAME_ISSUER_FLAG : 0, NULL, name, size);
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/* Decode a base64 encoded certificate blob and fill in
|
||||
* the cert structure with commonname, issuer and validity.
|
||||
* Returns false on error.
|
||||
*/
|
||||
static bool
|
||||
decode_certificate(struct cert_info *cert, const char *b64)
|
||||
{
|
||||
unsigned char *der = NULL;
|
||||
bool ret = false;
|
||||
|
||||
int len = Base64Decode(b64, (char **) &der);
|
||||
if (len < 0)
|
||||
{
|
||||
goto out;
|
||||
}
|
||||
|
||||
const CERT_CONTEXT *ctx =
|
||||
CertCreateCertificateContext(X509_ASN_ENCODING, der, (DWORD) len);
|
||||
|
||||
if (!ctx)
|
||||
{
|
||||
goto out;
|
||||
}
|
||||
cert->commonname = extract_name_entry(ctx, 0);
|
||||
cert->issuer = extract_name_entry(ctx, CERT_NAME_ISSUER_FLAG);
|
||||
cert->notAfter = LocalizedFileTime(&ctx->pCertInfo->NotAfter);
|
||||
CertFreeCertificateContext(ctx);
|
||||
|
||||
ret = true;
|
||||
|
||||
out:
|
||||
free(der);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Parse pkcs11-id message "'n', ID:'<id>', BLOB:'<cert>'"
|
||||
* and fill in data in pkcs11 enrty.
|
||||
* Returns index of the item on success, -1 on error.
|
||||
* On success, caller must free the entry after use.
|
||||
*/
|
||||
static UINT
|
||||
pkcs11_entry_parse(const char *data, struct pkcs11_list *l)
|
||||
{
|
||||
char *token = NULL;
|
||||
UINT index = (UINT) -1;
|
||||
const char *quotes = " '";
|
||||
struct pkcs11_entry *pe = NULL;
|
||||
|
||||
char *p = strdup(data);
|
||||
|
||||
if (!p)
|
||||
{
|
||||
goto out;
|
||||
}
|
||||
|
||||
token = strtok(p, ",");
|
||||
/* parse index */
|
||||
if (token)
|
||||
{
|
||||
StrTrimA(token, quotes);
|
||||
UINT i = strtoul(token, NULL, 10);
|
||||
if (i >= l->count) /* invalid entry number */
|
||||
{
|
||||
goto out;
|
||||
}
|
||||
index = i;
|
||||
}
|
||||
pe = &l->pe[index];
|
||||
|
||||
while ((token = strtok(NULL, ",")) != NULL)
|
||||
{
|
||||
char *tmp;
|
||||
if ((tmp = strstr(token, "ID:")) != NULL)
|
||||
{
|
||||
tmp += 3;
|
||||
StrTrimA(tmp, quotes);
|
||||
pe->id = strdup(tmp);
|
||||
}
|
||||
else if ((tmp = strstr(token, "BLOB:")) != NULL)
|
||||
{
|
||||
tmp += 5;
|
||||
StrTrimA(tmp, quotes);
|
||||
if (!decode_certificate(&pe->cert, tmp))
|
||||
{
|
||||
pkcs11_entry_free(pe);
|
||||
index = (UINT) -1;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
free(p);
|
||||
return index;
|
||||
}
|
||||
|
||||
/* send pkcs11-id to management-interface */
|
||||
static void
|
||||
pkcs11_id_send(connection_t *c, const char *id)
|
||||
{
|
||||
const char *format = "needstr 'pkcs11-id-request' '%s'";
|
||||
|
||||
size_t len = strlen(format) + (id ? strlen(id) : 0) + 1;
|
||||
char *cmd = malloc(len);
|
||||
if (cmd)
|
||||
{
|
||||
snprintf(cmd, len, format, id ? id : "");
|
||||
cmd[len-1] = '\0';
|
||||
ManagementCommand(c, cmd, NULL, regular);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteStatusLog(c, L"GUI> ", L"Out of memory in pkcs11_id_send", false);
|
||||
ManagementCommand(c, "needstr 'pkcs11-id-request' ''", NULL, regular);
|
||||
}
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
static INT_PTR CALLBACK QueryPkcs11DialogProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
/* Handle Need 'pkcs11-id-request' from management */
|
||||
void
|
||||
OnPkcs11(connection_t *c, UNUSED char *msg)
|
||||
{
|
||||
struct pkcs11_list *l = &c->pkcs11_list;
|
||||
|
||||
pkcs11_list_clear(l);
|
||||
l->selected = (UINT) -1; /* set selection to an invalid index */
|
||||
|
||||
/* prompt user to select a certificate */
|
||||
if (IDOK == LocalizedDialogBoxParam(ID_DLG_PKCS11_QUERY, QueryPkcs11DialogProc, (LPARAM)c)
|
||||
&& l->state & STATE_SELECTED
|
||||
&& l->selected < l->count)
|
||||
{
|
||||
pkcs11_id_send(c, l->pe[l->selected].id);
|
||||
}
|
||||
}
|
||||
|
||||
/* Callback when pkcs11-id-count is received */
|
||||
static void
|
||||
pkcs11_count_recv(connection_t *c, char *msg)
|
||||
{
|
||||
struct pkcs11_list *l = &c->pkcs11_list;
|
||||
if (msg && strbegins(msg, ">PKCS11ID-COUNT:"))
|
||||
{
|
||||
l->count = strtoul(msg+16, NULL, 10);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteStatusLog(c, L"GUI> ", L"Invalid pkcs11-id-count ignored", false);
|
||||
l->state &= ~STATE_GET_COUNT;
|
||||
}
|
||||
if (l->count == 0)
|
||||
{
|
||||
l->state |= STATE_FILLED;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Callback for receiving pkcs11 entry from daemon.
|
||||
* Expect msg = >PKCS11ID-ENTRY:'index', ID:'<id>', BLOB:'<cert>'
|
||||
*/
|
||||
static void
|
||||
pkcs11_entry_recv(connection_t *c, char *msg)
|
||||
{
|
||||
struct pkcs11_list *l = &c->pkcs11_list;
|
||||
UINT index = (UINT) -1;
|
||||
|
||||
if (msg && strbegins(msg, ">PKCS11ID-ENTRY:"))
|
||||
{
|
||||
index = pkcs11_entry_parse(msg+16, l);
|
||||
}
|
||||
|
||||
if (index == (UINT) -1)
|
||||
{
|
||||
WriteStatusLog(c, L"GUI> ", L"Invalid pkcs11 entry ignored.", false);
|
||||
return;
|
||||
}
|
||||
else if (index + 1 == l->count) /* done */
|
||||
{
|
||||
l->state |= STATE_FILLED;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper to populate the pkcs11 list by querying the daemon
|
||||
*
|
||||
* The requests are queued and completed asynchronously.
|
||||
* We return immediately if already in progress or
|
||||
* not yet ready to populate. This is called by
|
||||
* pkcs11_listview_fill until state & STATE_FILLED evaluates to 1.
|
||||
*
|
||||
* To recreate the list afresh, call pkcs11_list_clear() first.
|
||||
*/
|
||||
static void
|
||||
pkcs11_list_update(connection_t *c)
|
||||
{
|
||||
struct pkcs11_list *l = &c->pkcs11_list;
|
||||
|
||||
if ((l->state & STATE_GET_COUNT) == 0)
|
||||
{
|
||||
ManagementCommand(c, "pkcs11-id-count", pkcs11_count_recv, regular);
|
||||
l->state |= STATE_GET_COUNT;
|
||||
}
|
||||
else if (l->count > 0 && (l->state & STATE_GET_ENTRY) == 0)
|
||||
{
|
||||
if (!l->pe
|
||||
&& !pkcs11_list_alloc(l))
|
||||
{
|
||||
WriteStatusLog(c, L"GUI> ", L"Out of memory for pkcs11 entry list", false);
|
||||
l->count = 0;
|
||||
l->state |= STATE_FILLED;
|
||||
return;
|
||||
}
|
||||
/* required space = strlen("pkcs11-id-get ") + 10 + 1 = 25 */
|
||||
char cmd[25];
|
||||
for (UINT i = 0; i < l->count; i++)
|
||||
{
|
||||
_snprintf_0(cmd, "pkcs11-id-get %u", i);
|
||||
ManagementCommand(c, cmd, pkcs11_entry_recv, regular);
|
||||
}
|
||||
l->state |= STATE_GET_ENTRY;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Position widgets in pkcs11 list window using current dpi.
|
||||
* Takes client area width and height in screen pixels as input.
|
||||
*/
|
||||
void
|
||||
pkcs11_listview_resize(HWND hwnd, UINT w, UINT h)
|
||||
{
|
||||
MoveWindow(GetDlgItem(hwnd, ID_LVW_PKCS11), DPI_SCALE(20), DPI_SCALE(25),
|
||||
w - DPI_SCALE(40), h - DPI_SCALE(120), TRUE);
|
||||
MoveWindow(GetDlgItem(hwnd, ID_TXT_PKCS11), DPI_SCALE(20), DPI_SCALE(5),
|
||||
w-DPI_SCALE(30), DPI_SCALE(15), TRUE);
|
||||
MoveWindow(GetDlgItem(hwnd, ID_TXT_WARNING), DPI_SCALE(20), h - DPI_SCALE(80),
|
||||
w-DPI_SCALE(20), DPI_SCALE(30), TRUE);
|
||||
MoveWindow(GetDlgItem(hwnd, IDOK), DPI_SCALE(20), h - DPI_SCALE(30),
|
||||
DPI_SCALE(60), DPI_SCALE(23), TRUE);
|
||||
MoveWindow(GetDlgItem(hwnd, IDCANCEL), DPI_SCALE(90), h - DPI_SCALE(30),
|
||||
DPI_SCALE(60), DPI_SCALE(23), TRUE);
|
||||
MoveWindow(GetDlgItem(hwnd, IDRETRY), DPI_SCALE(200), h - DPI_SCALE(30),
|
||||
DPI_SCALE(60), DPI_SCALE(23), TRUE);
|
||||
|
||||
/* leave space for notafter and divide the rest among the two name fields */
|
||||
int cx0 = (w - DPI_SCALE(40) - DPI_SCALE(125))/2;
|
||||
cx0 = (cx0 > 80) ? cx0 : 80; /* ensure a reasonable minimum width */
|
||||
|
||||
int cx[3] = {cx0, cx0, DPI_SCALE(120)};
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
ListView_SetColumnWidth(GetDlgItem(hwnd, ID_LVW_PKCS11), i, cx[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* initialize the listview widget for displaying pkcs11 entries */
|
||||
static HWND
|
||||
pkcs11_listview_init(HWND parent)
|
||||
{
|
||||
HWND lv;
|
||||
RECT rc;
|
||||
|
||||
lv = GetDlgItem(parent, ID_LVW_PKCS11);
|
||||
if (!lv)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SendMessage(lv, LVM_SETEXTENDEDLISTVIEWSTYLE, 0, LVS_EX_FULLROWSELECT);
|
||||
|
||||
/* Use bold font for header */
|
||||
HFONT hf = (HFONT) SendMessage(lv, WM_GETFONT, 0, 0);
|
||||
if (hf)
|
||||
{
|
||||
LOGFONT lf;
|
||||
GetObject(hf, sizeof(LOGFONT), &lf);
|
||||
lf.lfWeight = FW_BOLD;
|
||||
|
||||
HFONT hfb = CreateFontIndirect(&lf);
|
||||
if (hfb)
|
||||
{
|
||||
SendMessage(ListView_GetHeader(lv), WM_SETFONT, (WPARAM)hfb, 1);
|
||||
SetProp(parent, L"header_font", (HANDLE)hfb);
|
||||
}
|
||||
}
|
||||
|
||||
/* Add column headings */
|
||||
int hdrs[] = {IDS_CERT_DISPLAYNAME, IDS_CERT_ISSUER, IDS_CERT_NOTAFTER};
|
||||
|
||||
LVCOLUMNW lvc;
|
||||
lvc.mask = LVCF_TEXT | LVCF_SUBITEM;
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
lvc.iSubItem = i;
|
||||
lvc.pszText = LoadLocalizedString(hdrs[i]);
|
||||
ListView_InsertColumn(lv, i, &lvc);
|
||||
}
|
||||
|
||||
GetClientRect(parent, &rc);
|
||||
pkcs11_listview_resize(parent, rc.right-rc.left, rc.bottom-rc.top);
|
||||
|
||||
EnableWindow(lv, FALSE); /* disable until filled in */
|
||||
EnableWindow(GetDlgItem(parent, IDOK), FALSE);
|
||||
EnableWindow(GetDlgItem(parent, IDRETRY), FALSE);
|
||||
|
||||
return lv;
|
||||
}
|
||||
|
||||
/* Populate the pkcs11 list and listview widget. Unless the state
|
||||
* of the list evaluates to STATE_FILLED, a callback to this
|
||||
* is requeued. Meant to be used as a Timer callback.
|
||||
*/
|
||||
static void CALLBACK
|
||||
pkcs11_listview_fill(HWND hwnd, UINT UNUSED msg, UINT_PTR id, DWORD UNUSED now)
|
||||
{
|
||||
connection_t *c = (connection_t *) GetProp(hwnd, cfgProp);
|
||||
struct pkcs11_list *l = &c->pkcs11_list;
|
||||
|
||||
HWND lv = GetDlgItem(hwnd, ID_LVW_PKCS11);
|
||||
|
||||
LVITEMW lvi = {0};
|
||||
lvi.mask = LVIF_TEXT|LVIF_PARAM;
|
||||
|
||||
if ((l->state & STATE_FILLED) == 0)
|
||||
{
|
||||
/* request list update and set a timer to call this routine again */
|
||||
pkcs11_list_update(c);
|
||||
SetTimer(hwnd, id, 100, pkcs11_listview_fill);
|
||||
}
|
||||
else
|
||||
{
|
||||
int pos;
|
||||
for (UINT i = 0; i < l->count; i++)
|
||||
{
|
||||
lvi.iItem = i;
|
||||
lvi.iSubItem = 0;
|
||||
lvi.pszText = l->pe[i].cert.commonname;
|
||||
lvi.lParam = (LPARAM) i;
|
||||
pos = ListView_InsertItem(lv, &lvi);
|
||||
|
||||
ListView_SetItemText(lv, pos, 1, l->pe[i].cert.issuer);
|
||||
ListView_SetItemText(lv, pos, 2, l->pe[i].cert.notAfter);
|
||||
}
|
||||
|
||||
if (l->count == 0)
|
||||
{
|
||||
/* no certificates -- show a message and let user retry */
|
||||
SetDlgItemTextW(hwnd, ID_TXT_WARNING, LoadLocalizedString(IDS_ERR_NO_PKCS11));
|
||||
EnableWindow(GetDlgItem(hwnd, IDRETRY), TRUE);
|
||||
}
|
||||
else
|
||||
{
|
||||
EnableWindow(lv, TRUE);
|
||||
SetFocus(lv);
|
||||
EnableWindow(GetDlgItem(hwnd, IDOK), TRUE);
|
||||
EnableWindow(GetDlgItem(hwnd, IDRETRY), TRUE);
|
||||
SendMessage(GetDlgItem(hwnd, IDOK), BM_SETSTYLE, BS_DEFPUSHBUTTON, TRUE);
|
||||
SendMessage(GetDlgItem(hwnd, IDCANCEL), BM_SETSTYLE, BS_PUSHBUTTON, TRUE);
|
||||
SendMessage(GetDlgItem(hwnd, IDRETRY), BM_SETSTYLE, BS_PUSHBUTTON, TRUE);
|
||||
}
|
||||
|
||||
/* if there is only one item, select it by default */
|
||||
if (l->count == 1)
|
||||
{
|
||||
ListView_SetItemState(lv, pos, LVIS_SELECTED, LVIS_SELECTED);
|
||||
}
|
||||
|
||||
KillTimer(hwnd, id);
|
||||
}
|
||||
}
|
||||
|
||||
/* Reset the listview, clear the list and initiate a fresh scan
|
||||
* without closing the dialog.
|
||||
*/
|
||||
static void
|
||||
pkcs11_listview_reset(HWND parent)
|
||||
{
|
||||
connection_t *c = (connection_t *) GetProp(parent, cfgProp);
|
||||
struct pkcs11_list *l = &c->pkcs11_list;
|
||||
HWND lv = GetDlgItem(parent, ID_LVW_PKCS11);
|
||||
|
||||
/* ensure the list is not being built and the widget exists */
|
||||
if ((l->state & STATE_FILLED) == 0 || !lv)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* clear the list and listview */
|
||||
pkcs11_list_clear(l);
|
||||
|
||||
EnableWindow(lv, FALSE);
|
||||
EnableWindow(GetDlgItem(parent, IDOK), FALSE);
|
||||
EnableWindow(GetDlgItem(parent, IDRETRY), FALSE);
|
||||
|
||||
ListView_DeleteAllItems(lv);
|
||||
SetDlgItemTextW(parent, ID_TXT_WARNING, L"");
|
||||
|
||||
/* initiate a rebuild of the list */
|
||||
SetTimer(parent, 0, 100, pkcs11_listview_fill);
|
||||
}
|
||||
|
||||
/* Dialog proc for querying pkcs11 */
|
||||
static INT_PTR CALLBACK
|
||||
QueryPkcs11DialogProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
connection_t *c;
|
||||
|
||||
switch (msg)
|
||||
{
|
||||
case WM_INITDIALOG:
|
||||
c = (connection_t *) lParam;
|
||||
SetProp(hwndDlg, cfgProp, (HANDLE)lParam);
|
||||
SetStatusWinIcon(hwndDlg, ID_ICO_APP);
|
||||
|
||||
/* init the listview and schedule a call to listview_fill */
|
||||
if (pkcs11_listview_init(hwndDlg))
|
||||
{
|
||||
SetTimer(hwndDlg, 0, 50, pkcs11_listview_fill);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteStatusLog(c, L"GUI> ", L"Error initializing pkcs11 selection dialog.", false);
|
||||
EndDialog(hwndDlg, wParam);
|
||||
}
|
||||
return TRUE;
|
||||
|
||||
case WM_COMMAND:
|
||||
c = (connection_t *) GetProp(hwndDlg, cfgProp);
|
||||
if (LOWORD(wParam) == IDOK)
|
||||
{
|
||||
HWND lv = GetDlgItem(hwndDlg, ID_LVW_PKCS11);
|
||||
int id = (int) ListView_GetNextItem(lv, -1, LVNI_ALL|LVNI_SELECTED);
|
||||
LVITEM lvi = {.iItem = id, .mask = LVIF_PARAM};
|
||||
if (id >= 0 && ListView_GetItem(lv, &lvi))
|
||||
{
|
||||
c->pkcs11_list.selected = (UINT) lvi.lParam;
|
||||
c->pkcs11_list.state |= STATE_SELECTED;
|
||||
}
|
||||
else if (c->pkcs11_list.count > 0)
|
||||
{
|
||||
/* No selection -- show an error message */
|
||||
SetDlgItemTextW(hwndDlg, ID_TXT_WARNING, LoadLocalizedString(IDS_ERR_SELECT_PKCS11));
|
||||
return TRUE;
|
||||
}
|
||||
EndDialog(hwndDlg, wParam);
|
||||
return TRUE;
|
||||
}
|
||||
else if (LOWORD(wParam) == IDCANCEL)
|
||||
{
|
||||
StopOpenVPN(c);
|
||||
EndDialog(hwndDlg, wParam);
|
||||
return TRUE;
|
||||
}
|
||||
else if (LOWORD(wParam) == IDRETRY)
|
||||
{
|
||||
pkcs11_listview_reset(hwndDlg);
|
||||
return TRUE;
|
||||
}
|
||||
break;
|
||||
|
||||
case WM_CTLCOLORSTATIC:
|
||||
if (GetDlgCtrlID((HWND) lParam) == ID_TXT_WARNING)
|
||||
{
|
||||
HBRUSH br = (HBRUSH) DefWindowProc(hwndDlg, msg, wParam, lParam);
|
||||
COLORREF clr = o.clr_warning;
|
||||
SetTextColor((HDC) wParam, clr);
|
||||
return (INT_PTR) br;
|
||||
}
|
||||
break;
|
||||
|
||||
case WM_SIZE:
|
||||
pkcs11_listview_resize(hwndDlg, LOWORD(lParam), HIWORD(lParam));
|
||||
InvalidateRect(hwndDlg, NULL, TRUE);
|
||||
return FALSE;
|
||||
|
||||
case WM_NOTIFY:
|
||||
if (((NMHDR *)lParam)->idFrom == ID_LVW_PKCS11)
|
||||
{
|
||||
NMITEMACTIVATE *ln = (NMITEMACTIVATE *) lParam;
|
||||
if (ln->iItem >= 0 && ln->uNewState & LVNI_SELECTED)
|
||||
{
|
||||
/* remove the no-selection warning */
|
||||
SetDlgItemTextW(hwndDlg, ID_TXT_WARNING, L"");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case WM_CLOSE:
|
||||
c = (connection_t *) GetProp(hwndDlg, cfgProp);
|
||||
StopOpenVPN(c);
|
||||
EndDialog(hwndDlg, wParam);
|
||||
return TRUE;
|
||||
|
||||
case WM_NCDESTROY:
|
||||
RemoveProp(hwndDlg, cfgProp);
|
||||
HFONT hf = (HFONT) GetProp(hwndDlg, L"header_font");
|
||||
if (hf)
|
||||
{
|
||||
DeleteObject(hf);
|
||||
}
|
||||
RemoveProp(hwndDlg, cfgProp);
|
||||
break;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* OpenVPN-GUI -- A Windows GUI for OpenVPN.
|
||||
*
|
||||
* Copyright (C) 2022 Selva Nair <selva.nair@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program (see the file COPYING included with this
|
||||
* distribution); if not, write to the Free Software Foundation, Inc.,
|
||||
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef PKCS11_H
|
||||
#define PKCS11_H
|
||||
|
||||
struct pkcs11_list
|
||||
{
|
||||
unsigned int count; /* number of pkcs11 entries */
|
||||
unsigned int selected; /* entry selected by user: -1 if no selection */
|
||||
unsigned int state; /* a flag indicating list filling status */
|
||||
struct pkcs11_entry *pe; /* array of pkcs11-id entries */
|
||||
};
|
||||
|
||||
struct connection;
|
||||
/**
|
||||
* Callback for Need 'pkcs11-id-request' notification from management
|
||||
* @param c pointer to connection profile
|
||||
* @param msg string received from the management
|
||||
*/
|
||||
void OnPkcs11(struct connection *c, char *msg);
|
||||
|
||||
/**
|
||||
* Clear pkcs11 entry list and release memory.
|
||||
* @param l pointer to the list
|
||||
*
|
||||
*/
|
||||
void pkcs11_list_clear(struct pkcs11_list *l);
|
||||
|
||||
#endif
|
|
@ -290,6 +290,21 @@ BEGIN
|
|||
PUSHBUTTON "&Cancel", IDCANCEL, 90, 76, 52, 14
|
||||
END
|
||||
|
||||
/* Query PKCS11-ID Dialog */
|
||||
ID_DLG_PKCS11_QUERY DIALOGEX 6, 18, 340, 242
|
||||
STYLE WS_SIZEBOX| WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | DS_CENTER | DS_SETFONT
|
||||
CAPTION "Select Certificate"
|
||||
FONT 8, "Segoe UI"
|
||||
LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT
|
||||
BEGIN
|
||||
LTEXT "PKCS11 Certificates available:", ID_TXT_PKCS11, 17, 12, 171, 12
|
||||
CONTROL "", ID_LVW_PKCS11, "SysListView32", LVS_REPORT | LVS_SINGLESEL | WS_BORDER | WS_TABSTOP, 17, 25, 171,150
|
||||
PUSHBUTTON "&OK", IDOK, 20, 200, 50, 14, BS_DEFPUSHBUTTON | WS_TABSTOP
|
||||
PUSHBUTTON "&Cancel", IDCANCEL, 90, 200, 50, 14, BS_PUSHBUTTON | WS_TABSTOP
|
||||
PUSHBUTTON "&Retry", IDRETRY, 160, 200, 50, 14, BS_PUSHBUTTON | WS_TABSTOP
|
||||
LTEXT "", ID_TXT_WARNING, 6, 222, 190, 10
|
||||
END
|
||||
|
||||
STRINGTABLE
|
||||
LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT
|
||||
BEGIN
|
||||
|
@ -551,4 +566,11 @@ once as Administrator to update the registry."
|
|||
|
||||
/* AS profile import */
|
||||
IDS_ERR_URL_IMPORT_PROFILE "Error fetching profile from URL: [%d] %ls"
|
||||
|
||||
IDS_ERR_NO_PKCS11 "No certificates found. If you have a token insert it and press retry."
|
||||
IDS_ERR_SELECT_PKCS11 "No certificates selected."
|
||||
/* column headers for pkcs11 certificate list */
|
||||
IDS_CERT_DISPLAYNAME "Issued to"
|
||||
IDS_CERT_ISSUER "Issued by"
|
||||
IDS_CERT_NOTAFTER "Valid until"
|
||||
END
|
||||
|
|
Loading…
Reference in New Issue