Skip to content

Commit e84ada2

Browse files
committed
Log security error automatically
Log the error automatically when the signature & cetification verification fails: 1. Write error log (the security part) into %LOCALUSERDATA%\WinGUp\log\securityError.log, and add "-errLogPath=" argument to make it overridable. 2. Add the download URL + sha256 value of the downloaded file in the log. 3. Log also if the download URL is not what we expected. Fix #98, close #100
1 parent f15e9a4 commit e84ada2

File tree

5 files changed

+234
-66
lines changed

5 files changed

+234
-66
lines changed

src/Common.cpp

Lines changed: 136 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,146 @@
2323

2424
using namespace std;
2525

26-
void writeLog(const wchar_t* logFileName, const wchar_t* logSuffix, const wchar_t* log2write)
26+
void expandEnv(wstring& s)
27+
{
28+
wchar_t buffer[MAX_PATH] = { '\0' };
29+
// This returns the resulting string length or 0 in case of error.
30+
DWORD ret = ExpandEnvironmentStrings(s.c_str(), buffer, static_cast<DWORD>(std::size(buffer)));
31+
if (ret != 0)
32+
{
33+
if (ret == static_cast<DWORD>(lstrlen(buffer) + 1))
34+
{
35+
s = buffer;
36+
}
37+
else
38+
{
39+
// Buffer was too small, try with a bigger buffer of the required size.
40+
std::vector<wchar_t> buffer2(ret, 0);
41+
ret = ExpandEnvironmentStrings(s.c_str(), buffer2.data(), static_cast<DWORD>(buffer2.size()));
42+
s = buffer2.data();
43+
}
44+
}
45+
}
46+
47+
namespace
48+
{
49+
constexpr wchar_t timeFmtEscapeChar = 0x1;
50+
constexpr wchar_t middayFormat[] = L"tt";
51+
52+
// Returns AM/PM string defined by the system locale for the specified time.
53+
// This string may be empty or customized.
54+
wstring getMiddayString(const wchar_t* localeName, const SYSTEMTIME& st)
55+
{
56+
wstring midday;
57+
midday.resize(MAX_PATH);
58+
int ret = GetTimeFormatEx(localeName, 0, &st, middayFormat, &midday[0], static_cast<int>(midday.size()));
59+
if (ret > 0)
60+
midday.resize(ret - 1); // Remove the null-terminator.
61+
else
62+
midday.clear();
63+
return midday;
64+
}
65+
66+
// Replaces conflicting time format specifiers by a special character.
67+
bool escapeTimeFormat(wstring& format)
68+
{
69+
bool modified = false;
70+
for (auto& ch : format)
71+
{
72+
if (ch == middayFormat[0])
73+
{
74+
ch = timeFmtEscapeChar;
75+
modified = true;
76+
}
77+
}
78+
return modified;
79+
}
80+
81+
// Replaces special time format characters by actual AM/PM string.
82+
void unescapeTimeFormat(wstring& format, const wstring& midday)
83+
{
84+
if (midday.empty())
85+
{
86+
auto it = std::remove(format.begin(), format.end(), timeFmtEscapeChar);
87+
if (it != format.end())
88+
format.erase(it, format.end());
89+
}
90+
else
91+
{
92+
size_t i = 0;
93+
while ((i = format.find(timeFmtEscapeChar, i)) != wstring::npos)
94+
{
95+
if (i + 1 < format.size() && format[i + 1] == timeFmtEscapeChar)
96+
{
97+
// 'tt' => AM/PM
98+
format.erase(i, std::size(middayFormat) - 1);
99+
format.insert(i, midday);
100+
}
101+
else
102+
{
103+
// 't' => A/P
104+
format[i] = midday[0];
105+
}
106+
}
107+
}
108+
}
109+
}
110+
111+
wstring getDateTimeStrFrom(const wstring& dateTimeFormat, const SYSTEMTIME& st)
112+
{
113+
const wchar_t* localeName = LOCALE_NAME_USER_DEFAULT;
114+
const DWORD flags = 0;
115+
116+
constexpr int bufferSize = MAX_PATH;
117+
wchar_t buffer[bufferSize] = {};
118+
int ret = 0;
119+
120+
121+
// 1. Escape 'tt' that means AM/PM or 't' that means A/P.
122+
// This is needed to avoid conflict with 'M' date format that stands for month.
123+
wstring newFormat = dateTimeFormat;
124+
const bool hasMiddayFormat = escapeTimeFormat(newFormat);
125+
126+
// 2. Format the time (h/m/s/t/H).
127+
ret = GetTimeFormatEx(localeName, flags, &st, newFormat.c_str(), buffer, bufferSize);
128+
if (ret != 0)
129+
{
130+
// 3. Format the date (d/y/g/M).
131+
// Now use the buffer as a format string to process the format specifiers not recognized by GetTimeFormatEx().
132+
ret = GetDateFormatEx(localeName, flags, &st, buffer, buffer, bufferSize, nullptr);
133+
}
134+
135+
if (ret != 0)
136+
{
137+
if (hasMiddayFormat)
138+
{
139+
// 4. Now format only the AM/PM string.
140+
const wstring midday = getMiddayString(localeName, st);
141+
wstring result = buffer;
142+
unescapeTimeFormat(result, midday);
143+
return result;
144+
}
145+
return buffer;
146+
}
147+
148+
return {};
149+
}
150+
151+
void writeLog(const wchar_t* logFileName, const wchar_t* logPrefix, const wchar_t* log2write)
27152
{
28153
FILE* f = _wfopen(logFileName, L"a+, ccs=UTF-16LE");
29154
if (f)
30155
{
31-
wstring log = logSuffix;
32-
log += log2write;
33-
log += L'\n';
34-
fwrite(log.c_str(), sizeof(log.c_str()[0]), log.length(), f);
156+
SYSTEMTIME currentTime = {};
157+
::GetLocalTime(&currentTime);
158+
wstring log2writeStr = getDateTimeStrFrom(L"yyyy-MM-dd HH:mm:ss", currentTime);
159+
160+
log2writeStr += L" ";
161+
log2writeStr += logPrefix;
162+
log2writeStr += L" ";
163+
log2writeStr += log2write;
164+
log2writeStr += L'\n';
165+
fwrite(log2writeStr.c_str(), sizeof(log2writeStr.c_str()[0]), log2writeStr.length(), f);
35166
fflush(f);
36167
fclose(f);
37168
}

src/Common.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
#include <string>
2020

21+
void expandEnv(std::wstring& s);
22+
std::wstring getDateTimeStrFrom(const std::wstring& dateTimeFormat, const SYSTEMTIME& st);
2123
void writeLog(const wchar_t* logFileName, const wchar_t* logSuffix, const wchar_t* log2write);
2224
std::wstring s2ws(const std::string& str);
2325
std::string ws2s(const std::wstring& wstr);

src/verifySignedfile.cpp

Lines changed: 37 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -26,27 +26,49 @@
2626
#include <sensapi.h>
2727
#include <iomanip>
2828
#include <sstream>
29+
#include <shlwapi.h>
30+
#include <shlobj_core.h>
2931
#include "verifySignedfile.h"
30-
#include "Common.h"
32+
3133

3234
using namespace std;
3335

3436

3537
// Debug use
3638
bool doLogCertifError = false;
3739

40+
void SecurityGuard::writeSecurityError(const std::wstring& prefix, const std::wstring& log2write) const
41+
{
42+
// Expand the environment variable
43+
wstring expandedLogFileName = _errLogPath;
44+
expandEnv(expandedLogFileName);
45+
46+
// Create the folder & sub-folders for the log file
47+
wchar_t logDir[MAX_PATH];
48+
lstrcpy(logDir, expandedLogFileName.c_str());
49+
::PathRemoveFileSpec(logDir);
50+
int result = SHCreateDirectoryEx(NULL, logDir, NULL);
51+
52+
// If folder doesn't exit or folder creation failed
53+
if (result != ERROR_SUCCESS && result != ERROR_ALREADY_EXISTS)
54+
{
55+
// process %TEMP% treatment
56+
wchar_t* fileName = ::PathFindFileName(expandedLogFileName.c_str());
57+
expandedLogFileName = L"%TEMP%\\";
58+
expandedLogFileName += fileName;
59+
expandEnv(expandedLogFileName);
60+
}
61+
62+
writeLog(expandedLogFileName.c_str(), prefix.c_str(), log2write.c_str());
63+
}
64+
3865
bool SecurityGuard::verifySignedBinary(const std::wstring& filepath)
3966
{
4067
wstring display_name;
4168
wstring key_id_hex;
4269
wstring subject;
4370
wstring authority_key_id_hex;
4471

45-
if (doLogCertifError)
46-
{
47-
writeLog(L"c:\\tmp\\certifError.log", L"verifySignedBinary: ", filepath.c_str());
48-
}
49-
5072
//
5173
// Signature verification
5274
//
@@ -69,9 +91,6 @@ bool SecurityGuard::verifySignedBinary(const std::wstring& filepath)
6991
if (!_doCheckRevocation)
7092
{
7193
winTEXTrust_data.fdwRevocationChecks = WTD_REVOKE_NONE;
72-
73-
if (doLogCertifError)
74-
writeLog(L"c:\\tmp\\certifError.log", L"verifySignedBinary: ", L"certificate revocation checking is disabled");
7594
}
7695
else
7796
{
@@ -87,9 +106,6 @@ bool SecurityGuard::verifySignedBinary(const std::wstring& filepath)
87106
if (!online)
88107
{
89108
winTEXTrust_data.fdwRevocationChecks = WTD_REVOKE_NONE;
90-
91-
if (doLogCertifError)
92-
writeLog(L"c:\\tmp\\certifError.log", L"verifySignedBinary: ", L"system is offline - certificate revocation won't be checked");
93109
}
94110
}
95111

@@ -105,17 +121,13 @@ bool SecurityGuard::verifySignedBinary(const std::wstring& filepath)
105121

106122
if (vtrust)
107123
{
108-
if (doLogCertifError)
109-
writeLog(L"c:\\tmp\\certifError.log", L"verifySignedBinary: ", L"trust verification failed");
110-
124+
writeSecurityError(filepath.c_str(), L": chain of trust verification failed");
111125
return false;
112126
}
113127

114128
if (t2)
115129
{
116-
if (doLogCertifError)
117-
writeLog(L"c:\\tmp\\certifError.log", L"verifySignedBinary: ", L"error encountered while cleaning up after WinVerifyTrust");
118-
130+
writeSecurityError(filepath.c_str(), L": error encountered while cleaning up after WinVerifyTrust");
119131
return false;
120132
}
121133
}
@@ -206,9 +218,6 @@ bool SecurityGuard::verifySignedBinary(const std::wstring& filepath)
206218
}
207219
key_id_hex = ss.str();
208220

209-
if (doLogCertifError)
210-
writeLog(L"c:\\tmp\\certifError.log", L"verifySignedBinary: ", key_id_hex.c_str());
211-
212221
// Getting the display name
213222
auto sze = ::CertGetNameString(context, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, NULL, 0);
214223
if (sze <= 1)
@@ -265,63 +274,44 @@ bool SecurityGuard::verifySignedBinary(const std::wstring& filepath)
265274
LocalFree(pAuthKeyIdInfo);
266275
}
267276
}
268-
else
269-
{
270-
// Authority Key Identifier extension not found
271-
if (doLogCertifError)
272-
writeLog(L"c:\\tmp\\certifError.log", L"Authority Key ID: ", L"Extension not found");
273-
}
274277
// --- End AKI Retrieval ---
275278

276279
}
277280
catch (const string& s) {
278-
if (doLogCertifError)
279-
{
280-
writeLog(L"c:\\tmp\\certifError.log", L" verifySignedBinary: error while getting certificate information: ", s2ws(s).c_str());
281-
}
281+
writeSecurityError((filepath + L" - error while getting certificate information: ").c_str(), s2ws(s).c_str());
282282
status = false;
283283
}
284284
catch (...) {
285285
// Unknown error
286-
if (doLogCertifError)
287-
writeLog(L"c:\\tmp\\certifError.log", L"verifySignedBinary: ", L"error while getting certificate information");
288-
286+
writeSecurityError(filepath.c_str(), L": Unknow error while getting certificate information");
289287
status = false;
290288
}
291289

292290
//
293-
// fields verifications - if status is true, and string to compare (from the parameter) is not empty, then do compare
291+
// fields verifications - if status is true, and demaded parameter string to compare (from the parameter) is not empty, then do compare
294292
//
295293
if (status && (!_signer_display_name.empty() && _signer_display_name != display_name))
296294
{
297295
status = false;
298-
299-
if (doLogCertifError)
300-
writeLog(L"c:\\tmp\\certifError.log", L"verifySignedBinary: ", L"Invalid certificate display name");
296+
writeSecurityError(filepath.c_str(), display_name + L": Invalid certificate display name");
301297
}
302298

303299
if (status && (!_signer_subject.empty() && _signer_subject != subject))
304300
{
305301
status = false;
306-
307-
if (doLogCertifError)
308-
writeLog(L"c:\\tmp\\certifError.log", L"verifySignedBinary: ", L"Invalid certificate subject");
302+
writeSecurityError(filepath.c_str(), subject + L": Invalid certificate subject");
309303
}
310304

311305
if (status && (!_signer_key_id.empty() && stringToUpper(_signer_key_id) != key_id_hex))
312306
{
313307
status = false;
314-
315-
if (doLogCertifError)
316-
writeLog(L"c:\\tmp\\certifError.log", L"verifySignedBinary: ", L"Invalid certificate key id");
308+
writeSecurityError(filepath.c_str(), key_id_hex + L": Invalid certificate key id");
317309
}
318310

319311
if (status && (!_authority_key_id.empty() && stringToUpper(_authority_key_id) != authority_key_id_hex))
320312
{
321313
status = false;
322-
323-
if (doLogCertifError)
324-
writeLog(L"c:\\tmp\\certifError.log", L"verifySignedBinary: ", L"Invalid authority key id");
314+
writeSecurityError(filepath.c_str(), authority_key_id_hex + L": Invalid authority key id");
325315
}
326316

327317
// Clean up.

src/verifySignedfile.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747

4848
#include <string>
4949
#include <vector>
50+
#include "Common.h"
5051

5152
class SecurityGuard final
5253
{
@@ -61,6 +62,11 @@ class SecurityGuard final
6162
void setKeyId(const std::wstring& signer_key_id) { _signer_key_id = signer_key_id; }
6263
void setAuthorityKeyId(const std::wstring& authority_key_id) { _authority_key_id = authority_key_id; }
6364

65+
void setErrLogPath(std::wstring& errLogPath) { _errLogPath = errLogPath; }
66+
std::wstring errLogPath() const { return _errLogPath; }
67+
68+
void writeSecurityError(const std::wstring& prefix, const std::wstring& log2write) const;
69+
6470
private:
6571
// Code signing certificate
6672
std::wstring _signer_display_name; // = L"Notepad++"
@@ -70,5 +76,7 @@ class SecurityGuard final
7076

7177
bool _doCheckRevocation = false;
7278
bool _doCheckChainOfTrust = false;
79+
80+
std::wstring _errLogPath = L"%LOCALAPPDATA%\\WinGUp\\log\\securityError.log"; // By default, but overrideable
7381
};
7482

0 commit comments

Comments
 (0)