diff --git a/src/Common.cpp b/src/Common.cpp index 6f4624fc..0adc7cc4 100644 --- a/src/Common.cpp +++ b/src/Common.cpp @@ -23,15 +23,146 @@ using namespace std; -void writeLog(const wchar_t* logFileName, const wchar_t* logSuffix, const wchar_t* log2write) +void expandEnv(wstring& s) +{ + wchar_t buffer[MAX_PATH] = { '\0' }; + // This returns the resulting string length or 0 in case of error. + DWORD ret = ExpandEnvironmentStrings(s.c_str(), buffer, static_cast(std::size(buffer))); + if (ret != 0) + { + if (ret == static_cast(lstrlen(buffer) + 1)) + { + s = buffer; + } + else + { + // Buffer was too small, try with a bigger buffer of the required size. + std::vector buffer2(ret, 0); + ret = ExpandEnvironmentStrings(s.c_str(), buffer2.data(), static_cast(buffer2.size())); + s = buffer2.data(); + } + } +} + +namespace +{ + constexpr wchar_t timeFmtEscapeChar = 0x1; + constexpr wchar_t middayFormat[] = L"tt"; + + // Returns AM/PM string defined by the system locale for the specified time. + // This string may be empty or customized. + wstring getMiddayString(const wchar_t* localeName, const SYSTEMTIME& st) + { + wstring midday; + midday.resize(MAX_PATH); + int ret = GetTimeFormatEx(localeName, 0, &st, middayFormat, &midday[0], static_cast(midday.size())); + if (ret > 0) + midday.resize(ret - 1); // Remove the null-terminator. + else + midday.clear(); + return midday; + } + + // Replaces conflicting time format specifiers by a special character. + bool escapeTimeFormat(wstring& format) + { + bool modified = false; + for (auto& ch : format) + { + if (ch == middayFormat[0]) + { + ch = timeFmtEscapeChar; + modified = true; + } + } + return modified; + } + + // Replaces special time format characters by actual AM/PM string. + void unescapeTimeFormat(wstring& format, const wstring& midday) + { + if (midday.empty()) + { + auto it = std::remove(format.begin(), format.end(), timeFmtEscapeChar); + if (it != format.end()) + format.erase(it, format.end()); + } + else + { + size_t i = 0; + while ((i = format.find(timeFmtEscapeChar, i)) != wstring::npos) + { + if (i + 1 < format.size() && format[i + 1] == timeFmtEscapeChar) + { + // 'tt' => AM/PM + format.erase(i, std::size(middayFormat) - 1); + format.insert(i, midday); + } + else + { + // 't' => A/P + format[i] = midday[0]; + } + } + } + } +} + +wstring getDateTimeStrFrom(const wstring& dateTimeFormat, const SYSTEMTIME& st) +{ + const wchar_t* localeName = LOCALE_NAME_USER_DEFAULT; + const DWORD flags = 0; + + constexpr int bufferSize = MAX_PATH; + wchar_t buffer[bufferSize] = {}; + int ret = 0; + + + // 1. Escape 'tt' that means AM/PM or 't' that means A/P. + // This is needed to avoid conflict with 'M' date format that stands for month. + wstring newFormat = dateTimeFormat; + const bool hasMiddayFormat = escapeTimeFormat(newFormat); + + // 2. Format the time (h/m/s/t/H). + ret = GetTimeFormatEx(localeName, flags, &st, newFormat.c_str(), buffer, bufferSize); + if (ret != 0) + { + // 3. Format the date (d/y/g/M). + // Now use the buffer as a format string to process the format specifiers not recognized by GetTimeFormatEx(). + ret = GetDateFormatEx(localeName, flags, &st, buffer, buffer, bufferSize, nullptr); + } + + if (ret != 0) + { + if (hasMiddayFormat) + { + // 4. Now format only the AM/PM string. + const wstring midday = getMiddayString(localeName, st); + wstring result = buffer; + unescapeTimeFormat(result, midday); + return result; + } + return buffer; + } + + return {}; +} + +void writeLog(const wchar_t* logFileName, const wchar_t* logPrefix, const wchar_t* log2write) { FILE* f = _wfopen(logFileName, L"a+, ccs=UTF-16LE"); if (f) { - wstring log = logSuffix; - log += log2write; - log += L'\n'; - fwrite(log.c_str(), sizeof(log.c_str()[0]), log.length(), f); + SYSTEMTIME currentTime = {}; + ::GetLocalTime(¤tTime); + wstring log2writeStr = getDateTimeStrFrom(L"yyyy-MM-dd HH:mm:ss", currentTime); + + log2writeStr += L" "; + log2writeStr += logPrefix; + log2writeStr += L" "; + log2writeStr += log2write; + log2writeStr += L'\n'; + fwrite(log2writeStr.c_str(), sizeof(log2writeStr.c_str()[0]), log2writeStr.length(), f); fflush(f); fclose(f); } diff --git a/src/Common.h b/src/Common.h index 872433e9..1102ea0c 100644 --- a/src/Common.h +++ b/src/Common.h @@ -18,6 +18,8 @@ #include +void expandEnv(std::wstring& s); +std::wstring getDateTimeStrFrom(const std::wstring& dateTimeFormat, const SYSTEMTIME& st); void writeLog(const wchar_t* logFileName, const wchar_t* logSuffix, const wchar_t* log2write); std::wstring s2ws(const std::string& str); std::string ws2s(const std::wstring& wstr); diff --git a/src/verifySignedfile.cpp b/src/verifySignedfile.cpp index 1acb7a77..5048f901 100644 --- a/src/verifySignedfile.cpp +++ b/src/verifySignedfile.cpp @@ -26,8 +26,10 @@ #include #include #include +#include +#include #include "verifySignedfile.h" -#include "Common.h" + using namespace std; @@ -35,6 +37,31 @@ using namespace std; // Debug use bool doLogCertifError = false; +void SecurityGuard::writeSecurityError(const std::wstring& prefix, const std::wstring& log2write) const +{ + // Expand the environment variable + wstring expandedLogFileName = _errLogPath; + expandEnv(expandedLogFileName); + + // Create the folder & sub-folders for the log file + wchar_t logDir[MAX_PATH]; + lstrcpy(logDir, expandedLogFileName.c_str()); + ::PathRemoveFileSpec(logDir); + int result = SHCreateDirectoryEx(NULL, logDir, NULL); + + // If folder doesn't exit or folder creation failed + if (result != ERROR_SUCCESS && result != ERROR_ALREADY_EXISTS) + { + // process %TEMP% treatment + wchar_t* fileName = ::PathFindFileName(expandedLogFileName.c_str()); + expandedLogFileName = L"%TEMP%\\"; + expandedLogFileName += fileName; + expandEnv(expandedLogFileName); + } + + writeLog(expandedLogFileName.c_str(), prefix.c_str(), log2write.c_str()); +} + bool SecurityGuard::verifySignedBinary(const std::wstring& filepath) { wstring display_name; @@ -42,11 +69,6 @@ bool SecurityGuard::verifySignedBinary(const std::wstring& filepath) wstring subject; wstring authority_key_id_hex; - if (doLogCertifError) - { - writeLog(L"c:\\tmp\\certifError.log", L"verifySignedBinary: ", filepath.c_str()); - } - // // Signature verification // @@ -69,9 +91,6 @@ bool SecurityGuard::verifySignedBinary(const std::wstring& filepath) if (!_doCheckRevocation) { winTEXTrust_data.fdwRevocationChecks = WTD_REVOKE_NONE; - - if (doLogCertifError) - writeLog(L"c:\\tmp\\certifError.log", L"verifySignedBinary: ", L"certificate revocation checking is disabled"); } else { @@ -87,9 +106,6 @@ bool SecurityGuard::verifySignedBinary(const std::wstring& filepath) if (!online) { winTEXTrust_data.fdwRevocationChecks = WTD_REVOKE_NONE; - - if (doLogCertifError) - writeLog(L"c:\\tmp\\certifError.log", L"verifySignedBinary: ", L"system is offline - certificate revocation won't be checked"); } } @@ -105,17 +121,13 @@ bool SecurityGuard::verifySignedBinary(const std::wstring& filepath) if (vtrust) { - if (doLogCertifError) - writeLog(L"c:\\tmp\\certifError.log", L"verifySignedBinary: ", L"trust verification failed"); - + writeSecurityError(filepath.c_str(), L": chain of trust verification failed"); return false; } if (t2) { - if (doLogCertifError) - writeLog(L"c:\\tmp\\certifError.log", L"verifySignedBinary: ", L"error encountered while cleaning up after WinVerifyTrust"); - + writeSecurityError(filepath.c_str(), L": error encountered while cleaning up after WinVerifyTrust"); return false; } } @@ -206,9 +218,6 @@ bool SecurityGuard::verifySignedBinary(const std::wstring& filepath) } key_id_hex = ss.str(); - if (doLogCertifError) - writeLog(L"c:\\tmp\\certifError.log", L"verifySignedBinary: ", key_id_hex.c_str()); - // Getting the display name auto sze = ::CertGetNameString(context, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, NULL, 0); if (sze <= 1) @@ -265,63 +274,44 @@ bool SecurityGuard::verifySignedBinary(const std::wstring& filepath) LocalFree(pAuthKeyIdInfo); } } - else - { - // Authority Key Identifier extension not found - if (doLogCertifError) - writeLog(L"c:\\tmp\\certifError.log", L"Authority Key ID: ", L"Extension not found"); - } // --- End AKI Retrieval --- } catch (const string& s) { - if (doLogCertifError) - { - writeLog(L"c:\\tmp\\certifError.log", L" verifySignedBinary: error while getting certificate information: ", s2ws(s).c_str()); - } + writeSecurityError((filepath + L" - error while getting certificate information: ").c_str(), s2ws(s).c_str()); status = false; } catch (...) { // Unknown error - if (doLogCertifError) - writeLog(L"c:\\tmp\\certifError.log", L"verifySignedBinary: ", L"error while getting certificate information"); - + writeSecurityError(filepath.c_str(), L": Unknow error while getting certificate information"); status = false; } // - // fields verifications - if status is true, and string to compare (from the parameter) is not empty, then do compare + // fields verifications - if status is true, and demaded parameter string to compare (from the parameter) is not empty, then do compare // if (status && (!_signer_display_name.empty() && _signer_display_name != display_name)) { status = false; - - if (doLogCertifError) - writeLog(L"c:\\tmp\\certifError.log", L"verifySignedBinary: ", L"Invalid certificate display name"); + writeSecurityError(filepath.c_str(), display_name + L": Invalid certificate display name"); } if (status && (!_signer_subject.empty() && _signer_subject != subject)) { status = false; - - if (doLogCertifError) - writeLog(L"c:\\tmp\\certifError.log", L"verifySignedBinary: ", L"Invalid certificate subject"); + writeSecurityError(filepath.c_str(), subject + L": Invalid certificate subject"); } if (status && (!_signer_key_id.empty() && stringToUpper(_signer_key_id) != key_id_hex)) { status = false; - - if (doLogCertifError) - writeLog(L"c:\\tmp\\certifError.log", L"verifySignedBinary: ", L"Invalid certificate key id"); + writeSecurityError(filepath.c_str(), key_id_hex + L": Invalid certificate key id"); } if (status && (!_authority_key_id.empty() && stringToUpper(_authority_key_id) != authority_key_id_hex)) { status = false; - - if (doLogCertifError) - writeLog(L"c:\\tmp\\certifError.log", L"verifySignedBinary: ", L"Invalid authority key id"); + writeSecurityError(filepath.c_str(), authority_key_id_hex + L": Invalid authority key id"); } // Clean up. diff --git a/src/verifySignedfile.h b/src/verifySignedfile.h index 680ab8cc..3b7dc33b 100644 --- a/src/verifySignedfile.h +++ b/src/verifySignedfile.h @@ -47,6 +47,7 @@ #include #include +#include "Common.h" class SecurityGuard final { @@ -61,6 +62,11 @@ class SecurityGuard final void setKeyId(const std::wstring& signer_key_id) { _signer_key_id = signer_key_id; } void setAuthorityKeyId(const std::wstring& authority_key_id) { _authority_key_id = authority_key_id; } + void setErrLogPath(std::wstring& errLogPath) { _errLogPath = errLogPath; } + std::wstring errLogPath() const { return _errLogPath; } + + void writeSecurityError(const std::wstring& prefix, const std::wstring& log2write) const; + private: // Code signing certificate std::wstring _signer_display_name; // = L"Notepad++" @@ -70,5 +76,7 @@ class SecurityGuard final bool _doCheckRevocation = false; bool _doCheckChainOfTrust = false; + + std::wstring _errLogPath = L"%LOCALAPPDATA%\\WinGUp\\log\\securityError.log"; // By default, but overrideable }; diff --git a/src/winmain.cpp b/src/winmain.cpp index 7e3167ee..71fa17f4 100644 --- a/src/winmain.cpp +++ b/src/winmain.cpp @@ -99,6 +99,7 @@ static constexpr wchar_t FLAG_CHKCERT_NAME[] = L"-chkCertName="; static constexpr wchar_t FLAG_CHKCERT_SUBJECT[] = L"-chkCertSubject="; static constexpr wchar_t FLAG_CHKCERT_KEYID[] = L"-chkCertKeyId="; static constexpr wchar_t FLAG_CHKCERT_AUTHORITYKEYID[] = L"-chkCertAuthorityKeyId="; +static constexpr wchar_t FLAG_ERRLOGPATH[] = L"-errLogPath="; static constexpr wchar_t MSGID_HELP[] = L"Usage:\r\n\ @@ -134,17 +135,20 @@ gup [-vVERSION_VALUE] [-infoUrl=URL] [-forceDomain=URL_PREFIX]\r\n\ Update mode:\r\n\ \r\n\ gup [-vVERSION_VALUE] [-infoUrl=URL] [-chkCertSig=YES_NO] [-chkCertTrustChain]\r\n\ - [-chkCertRevoc] [-chkCertName=CERT_NAME] [-chkCertSubject=CERT_SUBNAME]\r\n\ + [-chkCertRevoc] [-chkCertName=\"CERT_NAME\"] [-chkCertSubject=\"CERT_SUBNAME\"]\r\n\ [-chkCertKeyId=CERT_KEYID] [-chkCertAuthorityKeyId=CERT_AUTHORITYKEYID]\r\n\ + [-errLogPath=\"YOUR\\ERR\\LOG\\PATH.LOG\"]\r\n\ \r\n\ -chkCertSig= : Enable signature check on downloaded binary with \"-chkCertSig=yes\".\r\n\ Otherwise all the other \"-chkCert*\" options will be ignored.\r\n\ - -chkCertTrustChain : Enable signature trust chain verification.\r\n\ + -chkCertTrustChain : Enable signature chain of trust verification.\r\n\ -chkCertRevoc : Enable the verification of certificate revocation state.\r\n\ -chkCertName= : Verify certificate name (quotes allowed for white-spaces).\r\n\ -chkCertSubject= : Verify subject name (quotes allowed for white-spaces).\r\n\ -chkCertKeyId= : Verify certificate key identifier.\r\n\ -chkCertAuthorityKeyId= : Verify certificate authority key identifier.\r\n\ + -errLogPath= : override the default error log path. The default value is:\r\n\ + \"%LOCALAPPDATA%\\WinGUp\\log\\securityError.log\"\r\n\ \r\n\ Download & unzip mode:\r\n\ \r\n\ @@ -995,7 +999,6 @@ bool downloadBinary(const wstring& urlFrom, const wstring& destTo, const wstring bool ok = true; if (!sha2HashToCheck.empty()) { - char sha2hashStr[65] = { '\0' }; std::string content = getFileContentA(ws2s(destTo).c_str()); if (content.empty()) { @@ -1005,6 +1008,7 @@ bool downloadBinary(const wstring& urlFrom, const wstring& destTo, const wstring } else { + char sha2hashStr[65] = { '\0' }; uint8_t sha2hash[32]; calc_sha_256(sha2hash, reinterpret_cast(content.c_str()), content.length()); @@ -1332,7 +1336,7 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR lpszCmdLine, int) signer_display_name = signer_display_name.substr(1, signer_display_name.length() - 2); } - signer_display_name = stringReplace(signer_display_name, L""", L"\""); + signer_display_name = stringReplace(signer_display_name, L"{QUOTE}", L"\""); securityGuard.setDisplayName(signer_display_name); } @@ -1345,7 +1349,7 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR lpszCmdLine, int) signer_subject = signer_subject.substr(1, signer_subject.length() - 2); } - signer_subject = stringReplace(signer_subject, L""", L"\""); + signer_subject = stringReplace(signer_subject, L"{QUOTE}", L"\""); securityGuard.setSubjectName(signer_subject); } @@ -1362,6 +1366,16 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR lpszCmdLine, int) securityGuard.setAuthorityKeyId(authority_key_id); } + wstring errLogPath; + if (getParamValFromString(FLAG_ERRLOGPATH, params, errLogPath)) + { + if (errLogPath.length() >= 2 && (errLogPath.front() == '"' && errLogPath.back() == '"')) + { + errLogPath = errLogPath.substr(1, errLogPath.length() - 2); + } + securityGuard.setErrLogPath(errLogPath); + } + // Object (gupParams) is moved here because we need app icon form configuration file GupParameters gupParams(L"gup.xml"); appIconFile = gupParams.getSoftwareIcon(); @@ -1644,24 +1658,19 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR lpszCmdLine, int) return 0; } - bool isDomaineOK = true; + wstring downloadURL; if (!forceDomain.empty()) { - const auto& downloadURL = gupDlInfo.getDownloadLocation(); + downloadURL = gupDlInfo.getDownloadLocation(); if (downloadURL.size() <= forceDomain.size() // download URL must be longer than forceDomain || downloadURL.compare(0, forceDomain.size(), forceDomain) != 0) // Check if forceDomain is a prefix of download URL { - isDomaineOK = false; + securityGuard.writeSecurityError(L"Download URL does not match the expected domain:", downloadURL); + return -1; } } - if (!isDomaineOK) - { - WRITE_LOG(GUP_LOG_FILENAME, L"return -1 in Npp Updater part: ", L"Domain is not matched for download URL. The file download won't be processed."); - return -1; - } - // // Process Update Info // @@ -1754,7 +1763,35 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR lpszCmdLine, int) bool isSecured = securityGuard.verifySignedBinary(dlDest); if (!isSecured) + { + wstring dlFileSha256; + + std::string content = getFileContentA(ws2s(dlDest).c_str()); + if (content.empty()) + { + dlFileSha256 = L"No SHA-256: the file is empty."; + } + else + { + char sha2hashStr[65] {}; + uint8_t sha2hash[32]; + calc_sha_256(sha2hash, reinterpret_cast(content.c_str()), content.length()); + + for (size_t i = 0; i < 32; i++) + { + sprintf(sha2hashStr + i * 2, "%02x", sha2hash[i]); + } + + dlFileSha256 = L"Downloaded file SHA-256: "; + dlFileSha256 += s2ws(sha2hashStr); + } + + wstring dlUrl = L"DownloadURL: "; + dlUrl += downloadURL; + + securityGuard.writeSecurityError(dlUrl, dlFileSha256); return -1; + } } // // Run executable bin