-
Notifications
You must be signed in to change notification settings - Fork 0
/
stundlg.cpp
357 lines (308 loc) · 14.3 KB
/
stundlg.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
/*
* Copyright (c) 2002-2003, Jeffrey A. Lawson and Bovine Networking
* Technologies, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* Neither the name of Bovine Networking Technologies, Inc. nor the names
* of its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// Transparent SSL Tunnel hooking.
// Jeff Lawson <jlawson@bovine.net>
// $Id: stundlg.cpp,v 1.4 2003/06/01 23:53:03 jlawson Exp $
#include "stuntour.h"
#include "resource.h"
#define WM_STUNTOUR_CLOSE (WM_USER + 1024)
//! Handle to the last-opened confirmation dialog. Only one is permitted open at any time.
static HWND hwndLastConfirmDialog = NULL;
//! Force all waiting messages for the specified window to be processed immediately.
static void PumpWaitingMessages(HWND hwnd)
{
MSG msg;
while (PeekMessage(&msg, hwnd, 0, 0, PM_REMOVE)) {
if (!IsDialogMessage(hwnd, &msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
//! Convert a binary buffer into a hexadecimal textual string.
std::string bin2hex(unsigned char *buffer, size_t buflen)
{
std::string retval;
if (buffer != NULL && buflen > 0) {
for (unsigned i = 0; i < buflen; i++) {
char hexbuf[3];
_snprintf(hexbuf, sizeof(hexbuf), "%02X", buffer[i]);
retval.append(hexbuf);
}
}
return retval;
}
//! Decides whether the certificate should be implicitly allowed without any
//! confirmation from the user.
/*!
* \param confinfo Pointer to structure containing identity of certificate.
* \return Returns true if the connection should be allowed without prompting.
* Otherwise the user should be asked whether to allow it.
*
* \sa ConfirmCertificateDialog
*/
bool CheckAllowCertificate(ConfirmationDialogData *confinfo)
{
DOUT(("stundlg: CheckAllowCertificate called\n"));
// OpenSSL will sometimes call the certificate verification routine
// multiple times when establishing a connection, but we should not
// prompt the user to confirm the same connection multiple times.
if (confinfo->stunnel->bCertificateAccepted) {
DOUT(("stundlg: Returning acceptance since this stunnel object has already been authorized.\n"));
return true;
}
#if 0
// If you trust OpenSSL's pre-verification step, then accept it.
if (confinfo->preverify_ok) {
DOUT(("stundlg: Returning acceptance since certificate preverification was ok.\n"));
return true;
}
#endif
#if 1
// Check the registry setting that forces us to accept all certificates.
// This is a pretty insecure mode since it allows anything.
do {
HKEY hkeySettings;
if (RegCreateKeyEx(HKEY_CURRENT_USER, REGKEYBASE, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE | KEY_READ, NULL, &hkeySettings, NULL) != ERROR_SUCCESS) {
break;
}
DWORD dwType;
char buffer[512];
DWORD dwBufferSize = sizeof(buffer);
bool AlwaysAllowAnyCert = false;
if (RegQueryValueEx(hkeySettings, "AlwaysAllowAnyCert", NULL, &dwType, (LPBYTE) buffer, &dwBufferSize) == ERROR_SUCCESS && (dwType == REG_DWORD)) {
AlwaysAllowAnyCert = (*reinterpret_cast<DWORD*>(buffer) != 0);
} else {
*reinterpret_cast<DWORD*>(buffer) = (AlwaysAllowAnyCert ? 1 : 0);
RegSetValueEx(hkeySettings, "AlwaysAllowAnyCert", 0, REG_DWORD,
reinterpret_cast<const BYTE *>(buffer), sizeof(DWORD) );
}
RegCloseKey(hkeySettings);
if (AlwaysAllowAnyCert) {
DOUT(("stundlg: Returning acceptance since the AlwaysAllowAnyCert mode is enabled.\n"));
return true;
}
} while (0);
#endif
// Check the registry to see if the user has explicitly accepted this
// certificate in the past.
unsigned char tmphash[SHA_DIGEST_LENGTH];
if (X509_pubkey_digest(confinfo->err_cert, EVP_sha1(), tmphash, NULL) != 0) {
std::string hexhash = bin2hex(tmphash, SHA_DIGEST_LENGTH);
std::string regpath = std::string(REGKEYBASE "\\AllowedCerts\\").append(hexhash);
HKEY hkeyAllowed;
if (RegOpenKeyEx(HKEY_CURRENT_USER, regpath.c_str(), 0, KEY_READ, &hkeyAllowed) == ERROR_SUCCESS) {
DOUT(("stundlg: Returning acceptance for persisted certificate.\n"));
RegCloseKey(hkeyAllowed);
return true;
}
}
return false; // don't know, so prompt the user about what to do.
}
//! Save the fact that the user has manually accepted a certificate.
void PersistAcceptanceForCertificate(ConfirmationDialogData *confinfo)
{
unsigned char tmphash[SHA_DIGEST_LENGTH];
if (X509_pubkey_digest(confinfo->err_cert, EVP_sha1(), tmphash, NULL) != 0) {
std::string hexhash = bin2hex(tmphash, SHA_DIGEST_LENGTH);
std::string regpath = std::string(REGKEYBASE "\\AllowedCerts\\").append(hexhash);
HKEY hkeyAllowed;
if (RegCreateKeyEx(HKEY_CURRENT_USER, regpath.c_str(), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE | KEY_READ, NULL, &hkeyAllowed, NULL) == ERROR_SUCCESS) {
DOUT(("stundlg: Saving acceptance for persisted certificate.\n"));
char buf[256];
X509_NAME_oneline(X509_get_subject_name(confinfo->err_cert), buf, sizeof(buf));
RegSetValueEx(hkeyAllowed, "SubjectName", 0, REG_SZ, reinterpret_cast<const BYTE*>(buf), static_cast<DWORD>(strlen(buf) + 1));
RegCloseKey(hkeyAllowed);
}
}
}
//! Construct a text string providing as much detail as possible for the
//! user to safely decide whether to accept the connection.
/*!
* The resulting text contains information about the certificate, the issuer
* of the certificate, the connection, and the automated pre-validation
* done by OpenSSL.
*
* \param certinfo Pointer to structure containing identity of certificate.
* \return Returns a string containing the text to be displayed.
*/
static std::string BuildConfirmationDetails(ConfirmationDialogData *confinfo)
{
char buf[256];
// Add the destination (IP Address and port) of the connection.
std::string strDestination = confinfo->stunnel->GetAddressAndPort();
// Add the name to which the certificate was issued.
X509_NAME_get_text_by_NID(X509_get_subject_name(confinfo->err_cert), NID_commonName, buf, sizeof(buf));
std::string strCommonName = buf;
X509_NAME_oneline(X509_get_subject_name(confinfo->err_cert), buf, sizeof(buf));
std::string strSubjectName = buf;
// Get the name of the issuer of the certificate.
X509_NAME_oneline(X509_get_issuer_name(confinfo->err_cert), buf, sizeof(buf));
std::string strIssuerName = buf;
bool bIssuerKnown;
if (!confinfo->preverify_ok && (confinfo->err == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT)) {
bIssuerKnown = false;
} else {
bIssuerKnown = true;
}
std::string textbuffer;
textbuffer.append("Destination: ");
textbuffer.append(strDestination);
textbuffer.append("\r\n\r\n");
textbuffer.append("Partial Subject Name:\r\n");
textbuffer.append(strCommonName);
textbuffer.append("\r\n\r\n");
textbuffer.append("Full Subject Name:\r\n");
textbuffer.append(strSubjectName);
textbuffer.append("\r\n\r\n");
textbuffer.append(bIssuerKnown ? "Known Issuer:\r\n" : "Unknown Issuer:\r\n");
textbuffer.append(strIssuerName);
textbuffer.append("\r\n\r\n");
/*
if (confinfo->depth > mydata->verify_depth) {
confinfo->preverify_ok = 0;
confinfo->err = X509_V_ERR_CERT_CHAIN_TOO_LONG;
X509_STORE_CTX_set_error(ctx, confinfo->err);
}
*/
if (!confinfo->preverify_ok) {
_snprintf(buf, sizeof(buf), "Verify error: %s (%s)\n",
X509_verify_cert_error_string(confinfo->err), TranslateX509Error(confinfo->err));
textbuffer.append(buf);
_snprintf(buf, sizeof(buf), "Chain verify depth: %d\n\n", confinfo->depth);
textbuffer.append(buf);
}
return textbuffer;
}
//! Internal dialog procedure handler for the confirmation dialog.
// TODO: need to close existing dialog when an attempt to create a second instance.
// TODO: need to close existing dialog when associated socket is closed.
static INT_PTR CALLBACK ConfirmCertificateDialogProc(
HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if (uMsg != WM_MOUSEMOVE) {
DOUT(("stundlg: DialogProc(hwndDlg:=%p, uMsg:=%08x, wParam:=%08x, lParam:=%08x)\n", (void*)hwndDlg, (int) uMsg, (int) wParam, (int) lParam));
}
switch (uMsg) {
case WM_INITDIALOG:
{
ConfirmationDialogData *confinfo = reinterpret_cast<ConfirmationDialogData*>(lParam);
SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG) (LONG_PTR) confinfo);
// Start the checkbox unchecked.
confinfo->bRememberChoice = false;
CheckDlgButton(hwndDlg, IDC_CHECKREMEMBER, BST_UNCHECKED);
// If there is another confirmation dialog already open, then close it.
if (IsWindow(hwndLastConfirmDialog)) {
DOUT(("stundlg: WM_INITDIALOG closing existing dialog instance hwndDlg=%p before continuing.\n", hwndLastConfirmDialog));
//SendMessage(hwndLastConfirmDialog, WM_COMMAND, MAKEWPARAM(BN_CLICKED, IDCANCEL), NULL);
SendMessage(hwndLastConfirmDialog, WM_STUNTOUR_CLOSE, 0, 0);
PumpWaitingMessages(hwndLastConfirmDialog);
}
hwndLastConfirmDialog = hwndDlg;
// Populate the details about the certficate.
std::string textbuffer = BuildConfirmationDetails(confinfo);
SetDlgItemText(hwndDlg, IDC_SERVERDETAILS, textbuffer.c_str());
return TRUE;
}
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDYES:
case IDNO:
case IDCANCEL: {
ConfirmationDialogData *confinfo = reinterpret_cast<ConfirmationDialogData*>((LONG_PTR) GetWindowLongPtr(hwndLastConfirmDialog, GWLP_USERDATA));
confinfo->bRememberChoice = (IsDlgButtonChecked(hwndLastConfirmDialog, IDC_CHECKREMEMBER) == BST_CHECKED);
EndDialog(hwndDlg, LOWORD(wParam));
return TRUE;
}
default: break;
}
break;
case WM_STUNTOUR_CLOSE: {
DOUT(("stundlg: Got close request for dialog %p\n", hwndDlg));
ConfirmationDialogData *confinfo = reinterpret_cast<ConfirmationDialogData*>((LONG_PTR) GetWindowLongPtr(hwndLastConfirmDialog, GWLP_USERDATA));
confinfo->bRememberChoice = (IsDlgButtonChecked(hwndLastConfirmDialog, IDC_CHECKREMEMBER) == BST_CHECKED);
EndDialog(hwndDlg, IDCANCEL);
return TRUE;
}
default: break;
}
return FALSE;
}
//! Displays a modal dialog box prompting the user to confirm a connection.
/*!
* We intentionally only allow one instance of this dialog box to be
* displayed at any time (even for unrelated socket connections). If this
* method is called and an existing confirmation dialog box is already
* open, the previous instance will be automatically closed with a
* result code of IDCANCEL.
*
* \param certinfo Pointer to structure containing identity of certificate.
* \return Returns one of the following:
* - IDYES User accepted certificate.
* - IDNO User rejected certificate.
* - IDCANCEL Dialog was dismissed (possibly automatically)
*/
DWORD ConfirmCertificateDialog(ConfirmationDialogData *certinfo)
{
DOUT(("stundlg: ConfirmCertificateDialog called\n"));
DWORD dwResult = (DWORD) DialogBoxParam(g_hInstance, MAKEINTRESOURCE(IDD_CONFIRM), GetOurParentWindow(), ConfirmCertificateDialogProc, (LPARAM) certinfo);
DOUT(("stundlg: ConfirmCertificateDialog return value is %d\n", (int) dwResult));
return dwResult;
}
//! Close the confirmation dialog if it is currently displayed and related
//! to the specified socket.
/*!
* This method is intended to be used when a socket is being explicitly
* closed by the application and it is no longer relevant to keep
* connection establishment confirmations related to it open.
*
* \param sock Socket handle to close dialogs for.
* \return Does not return any value.
*/
void CloseConfirmationDialogForSocket(SOCKET sock)
{
__try {
if (IsWindow(hwndLastConfirmDialog)) {
ConfirmationDialogData *confinfo = reinterpret_cast<ConfirmationDialogData*>((LONG_PTR) GetWindowLongPtr(hwndLastConfirmDialog, GWLP_USERDATA));
if (confinfo != NULL && confinfo->stunnel->GetSocket() == sock) {
DOUT(("stundlg: CloseConfirmationDialogForSocket closing existing dialog hwnd=%p for socket %d\n", hwndLastConfirmDialog, sock));
//SendMessage(hwndLastConfirmDialog, WM_COMMAND, MAKEWPARAM(BN_CLICKED, IDCANCEL), NULL);
SendMessage(hwndLastConfirmDialog, WM_STUNTOUR_CLOSE, 0, 0);
PumpWaitingMessages(hwndLastConfirmDialog);
hwndLastConfirmDialog = NULL;
}
}
} __except(EXCEPTION_EXECUTE_HANDLER) {
// nothing, ignore exceptions.
}
}