diff --git a/CMakeLists.txt b/CMakeLists.txt index ebb80fa..150dc86 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) \ No newline at end of file +endif(MSVC) diff --git a/Makefile.am b/Makefile.am index afdf729..d34e7fe 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 diff --git a/manage.c b/manage.c index a567af7..6ca6b21 100644 --- a/manage.c +++ b/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) { diff --git a/openvpn-gui-res.h b/openvpn-gui-res.h index 1380ccc..62319ff 100644 --- a/openvpn-gui-res.h +++ b/openvpn-gui-res.h @@ -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 */ diff --git a/openvpn.c b/openvpn.c index ce6e975..febbd96 100644 --- a/openvpn.c +++ b/openvpn.c @@ -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); diff --git a/options.h b/options.h index b31ff4d..5761655 100644 --- a/options.h +++ b/options.h @@ -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 */ diff --git a/pkcs11.c b/pkcs11.c new file mode 100644 index 0000000..692c21e --- /dev/null +++ b/pkcs11.c @@ -0,0 +1,634 @@ +/* + * OpenVPN-GUI -- A Windows GUI for OpenVPN. + * + * Copyright (C) 2022 Selva Nair + * + * 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 +#endif + +#include +#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 +#include +#include + +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:'', BLOB:''" + * 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:'', BLOB:'' + */ +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; +} diff --git a/pkcs11.h b/pkcs11.h new file mode 100644 index 0000000..001ad21 --- /dev/null +++ b/pkcs11.h @@ -0,0 +1,48 @@ +/* + * OpenVPN-GUI -- A Windows GUI for OpenVPN. + * + * Copyright (C) 2022 Selva Nair + * + * 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 diff --git a/res/openvpn-gui-res-en.rc b/res/openvpn-gui-res-en.rc index 05ee994..8e7ffc3 100644 --- a/res/openvpn-gui-res-en.rc +++ b/res/openvpn-gui-res-en.rc @@ -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