Add SHA256 checking of the authentity of used modules
In order to remove the code signing certificate dependency, SHA256 checking of the authentity of used modules (Scilexer.dll, GUP.exe and nppPluginList.dll) is added. See the followling link for the detail information: https://notepad-plus-plus.org/community/topic/17184/remove-code-signing-from-notepad
This commit is contained in:
parent
1f5ba1803f
commit
3baef49079
@ -38,16 +38,61 @@
|
||||
#include <iomanip>
|
||||
#include "VerifySignedFile.h"
|
||||
#include "Common.h"
|
||||
#include "sha-256.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
bool VerifySignedLibrary(const wstring& filepath,
|
||||
const wstring& cert_key_id_hex,
|
||||
const wstring& cert_subject,
|
||||
const wstring& cert_display_name,
|
||||
bool doCheckRevocation,
|
||||
bool doCheckChainOfTrust,
|
||||
bool displayErrorMessage)
|
||||
SecurityMode SecurityGard::_securityMode = sm_sha256;
|
||||
//SecurityMode SecurityGard::_securityMode = sm_certif;
|
||||
|
||||
SecurityGard::SecurityGard()
|
||||
{
|
||||
_scilexerSha256.push_back(TEXT("03c9177631d2b32de3d32c73a8841cf68fc2cb17f306825489dc3df98000db85")); // v3.5.6 32 bit
|
||||
_scilexerSha256.push_back(TEXT("9896c4089275e21412fd80421827912ebd80e357394b05145a613d190462e211")); // v3.5.6 64 bit
|
||||
|
||||
_gupSha256.push_back(TEXT("4c8191f511c2ad67148ef809b40c1108aaa074130547157c335a959404d8d6f6")); // v5.1 32 bit
|
||||
_gupSha256.push_back(TEXT("268a65829e86d5c3d324eea79b51e59f0a7d07c69d3ba0f700c9cb3aa772566f")); // v5.1 64 bit
|
||||
}
|
||||
|
||||
bool SecurityGard::checkModule(std::wstring filePath, NppModule module2check)
|
||||
{
|
||||
if (_securityMode == sm_certif)
|
||||
return verifySignedLibrary(filePath, module2check);
|
||||
else if (_securityMode == sm_sha256)
|
||||
return checkSha256(filePath, module2check);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SecurityGard::checkSha256(std::wstring filePath, NppModule module2check)
|
||||
{
|
||||
std::string content = getFileContent(filePath.c_str());
|
||||
|
||||
uint8_t sha2hash[32];
|
||||
calc_sha_256(sha2hash, reinterpret_cast<const uint8_t*>(content.c_str()), content.length());
|
||||
|
||||
wchar_t sha2hashStr[65] = { '\0' };
|
||||
for (size_t i = 0; i < 32; i++)
|
||||
wsprintf(sha2hashStr + i * 2, TEXT("%02x"), sha2hash[i]);
|
||||
|
||||
std::vector<std::wstring>* moduleSha256 = nullptr;
|
||||
if (module2check == nm_scilexer)
|
||||
moduleSha256 = &_scilexerSha256;
|
||||
else if (module2check == nm_gup)
|
||||
moduleSha256 = &_gupSha256;
|
||||
else if (module2check == nm_pluginList)
|
||||
moduleSha256 = &_pluginListSha256;
|
||||
else
|
||||
return false;
|
||||
|
||||
for (auto i : *moduleSha256)
|
||||
if (i == sha2hashStr)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SecurityGard::verifySignedLibrary(const std::wstring& filepath, NppModule module2check)
|
||||
{
|
||||
wstring display_name;
|
||||
wstring key_id_hex;
|
||||
@ -56,7 +101,7 @@ bool VerifySignedLibrary(const wstring& filepath,
|
||||
wstring dmsg(TEXT("VerifyLibrary: "));
|
||||
dmsg += filepath;
|
||||
dmsg += TEXT("\n");
|
||||
|
||||
|
||||
OutputDebugString(dmsg.c_str());
|
||||
|
||||
//
|
||||
@ -72,13 +117,13 @@ bool VerifySignedLibrary(const wstring& filepath,
|
||||
// Initialise WinTrust data
|
||||
WINTRUST_DATA winTEXTrust_data = { 0 };
|
||||
winTEXTrust_data.cbStruct = sizeof(winTEXTrust_data);
|
||||
winTEXTrust_data.dwUIChoice = WTD_UI_NONE; // do not display optional dialog boxes
|
||||
winTEXTrust_data.dwUnionChoice = WTD_CHOICE_FILE; // we are not checking catalog signed files
|
||||
winTEXTrust_data.dwStateAction = WTD_STATEACTION_VERIFY; // only checking
|
||||
winTEXTrust_data.dwUIChoice = WTD_UI_NONE; // do not display optional dialog boxes
|
||||
winTEXTrust_data.dwUnionChoice = WTD_CHOICE_FILE; // we are not checking catalog signed files
|
||||
winTEXTrust_data.dwStateAction = WTD_STATEACTION_VERIFY; // only checking
|
||||
winTEXTrust_data.fdwRevocationChecks = WTD_REVOKE_WHOLECHAIN; // verify the whole certificate chain
|
||||
winTEXTrust_data.pFile = &file_data;
|
||||
winTEXTrust_data.pFile = &file_data;
|
||||
|
||||
if (!doCheckRevocation)
|
||||
if (!_doCheckRevocation)
|
||||
{
|
||||
winTEXTrust_data.fdwRevocationChecks = WTD_REVOKE_NONE;
|
||||
OutputDebugString(TEXT("VerifyLibrary: certificate revocation checking is disabled\n"));
|
||||
@ -102,7 +147,7 @@ bool VerifySignedLibrary(const wstring& filepath,
|
||||
}
|
||||
}
|
||||
|
||||
if (doCheckChainOfTrust)
|
||||
if (_doCheckChainOfTrust)
|
||||
{
|
||||
// Verify signature and cert-chain validity
|
||||
GUID policy = WINTRUST_ACTION_GENERIC_VERIFY_V2;
|
||||
@ -128,52 +173,52 @@ bool VerifySignedLibrary(const wstring& filepath,
|
||||
//
|
||||
// Certificate verification
|
||||
//
|
||||
HCERTSTORE hStore = nullptr;
|
||||
HCRYPTMSG hMsg = nullptr;
|
||||
HCERTSTORE hStore = nullptr;
|
||||
HCRYPTMSG hMsg = nullptr;
|
||||
PCMSG_SIGNER_INFO pSignerInfo = nullptr;
|
||||
DWORD dwEncoding, dwContentType, dwFormatType;
|
||||
DWORD dwSignerInfo = 0L;
|
||||
bool status = true;
|
||||
bool status = true;
|
||||
|
||||
try {
|
||||
BOOL result = ::CryptQueryObject(CERT_QUERY_OBJECT_FILE, filepath.c_str(),
|
||||
CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, CERT_QUERY_FORMAT_FLAG_BINARY, 0,
|
||||
&dwEncoding, &dwContentType, &dwFormatType,
|
||||
&hStore, &hMsg, NULL);
|
||||
CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, CERT_QUERY_FORMAT_FLAG_BINARY, 0,
|
||||
&dwEncoding, &dwContentType, &dwFormatType,
|
||||
&hStore, &hMsg, NULL);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
throw wstring( TEXT("Checking certificate of ") ) + filepath + TEXT(" : ") + GetLastErrorAsString(GetLastError());
|
||||
throw wstring(TEXT("Checking certificate of ")) + filepath + TEXT(" : ") + GetLastErrorAsString(GetLastError());
|
||||
}
|
||||
|
||||
// Get signer information size.
|
||||
result = ::CryptMsgGetParam(hMsg, CMSG_SIGNER_INFO_PARAM, 0, NULL, &dwSignerInfo);
|
||||
if (!result)
|
||||
{
|
||||
throw wstring( TEXT("CryptMsgGetParam first call: ")) + GetLastErrorAsString(GetLastError());
|
||||
{
|
||||
throw wstring(TEXT("CryptMsgGetParam first call: ")) + GetLastErrorAsString(GetLastError());
|
||||
}
|
||||
|
||||
// Get Signer Information.
|
||||
pSignerInfo = (PCMSG_SIGNER_INFO)LocalAlloc(LPTR, dwSignerInfo);
|
||||
if (NULL == pSignerInfo )
|
||||
if (NULL == pSignerInfo)
|
||||
{
|
||||
throw wstring( TEXT("Failed to allocate memory for signature processing"));
|
||||
throw wstring(TEXT("Failed to allocate memory for signature processing"));
|
||||
}
|
||||
|
||||
result = ::CryptMsgGetParam(hMsg, CMSG_SIGNER_INFO_PARAM, 0, (PVOID)pSignerInfo, &dwSignerInfo);
|
||||
if (!result)
|
||||
{
|
||||
throw wstring( TEXT("CryptMsgGetParam: ")) + GetLastErrorAsString(GetLastError());
|
||||
throw wstring(TEXT("CryptMsgGetParam: ")) + GetLastErrorAsString(GetLastError());
|
||||
}
|
||||
|
||||
// Get the signer certificate from temporary certificate store.
|
||||
CERT_INFO cert_info = { 0 };
|
||||
cert_info.Issuer = pSignerInfo->Issuer;
|
||||
cert_info.Issuer = pSignerInfo->Issuer;
|
||||
cert_info.SerialNumber = pSignerInfo->SerialNumber;
|
||||
PCCERT_CONTEXT context = ::CertFindCertificateInStore( hStore, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, CERT_FIND_SUBJECT_CERT, (PVOID)&cert_info, NULL);
|
||||
PCCERT_CONTEXT context = ::CertFindCertificateInStore(hStore, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, CERT_FIND_SUBJECT_CERT, (PVOID)&cert_info, NULL);
|
||||
if (!context)
|
||||
{
|
||||
throw wstring( TEXT("Certificate context: ")) + GetLastErrorAsString(GetLastError());
|
||||
throw wstring(TEXT("Certificate context: ")) + GetLastErrorAsString(GetLastError());
|
||||
}
|
||||
|
||||
// Getting the full subject
|
||||
@ -192,51 +237,53 @@ bool VerifySignedLibrary(const wstring& filepath,
|
||||
|
||||
// Getting key_id
|
||||
DWORD key_id_sze = 0;
|
||||
if (!::CertGetCertificateContextProperty( context, CERT_KEY_IDENTIFIER_PROP_ID, NULL, &key_id_sze))
|
||||
if (!::CertGetCertificateContextProperty(context, CERT_KEY_IDENTIFIER_PROP_ID, NULL, &key_id_sze))
|
||||
{
|
||||
throw wstring( TEXT("x509 property not found")) + GetLastErrorAsString(GetLastError());
|
||||
throw wstring(TEXT("x509 property not found")) + GetLastErrorAsString(GetLastError());
|
||||
}
|
||||
|
||||
std::unique_ptr<BYTE[]> key_id_buff( new BYTE[key_id_sze] );
|
||||
if (!::CertGetCertificateContextProperty( context, CERT_KEY_IDENTIFIER_PROP_ID, key_id_buff.get(), &key_id_sze))
|
||||
std::unique_ptr<BYTE[]> key_id_buff(new BYTE[key_id_sze]);
|
||||
if (!::CertGetCertificateContextProperty(context, CERT_KEY_IDENTIFIER_PROP_ID, key_id_buff.get(), &key_id_sze))
|
||||
{
|
||||
throw wstring( TEXT("Getting certificate property problem.")) + GetLastErrorAsString(GetLastError());
|
||||
throw wstring(TEXT("Getting certificate property problem.")) + GetLastErrorAsString(GetLastError());
|
||||
}
|
||||
|
||||
wstringstream ss;
|
||||
for (unsigned i = 0; i < key_id_sze; i++)
|
||||
{
|
||||
ss << std::uppercase << std::setfill(TCHAR('0')) << std::setw(2) << std::hex
|
||||
<< key_id_buff[i];
|
||||
ss << std::uppercase << std::setfill(TCHAR('0')) << std::setw(2) << std::hex
|
||||
<< key_id_buff[i];
|
||||
}
|
||||
key_id_hex = ss.str();
|
||||
wstring dbg = key_id_hex + TEXT("\n");
|
||||
OutputDebugString( dbg.c_str() );
|
||||
OutputDebugString(dbg.c_str());
|
||||
|
||||
// Getting the display name
|
||||
auto sze = ::CertGetNameString( context, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, NULL, 0);
|
||||
auto sze = ::CertGetNameString(context, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, NULL, 0);
|
||||
if (sze <= 1)
|
||||
{
|
||||
throw wstring( TEXT("Getting data size problem.")) + GetLastErrorAsString(GetLastError());
|
||||
throw wstring(TEXT("Getting data size problem.")) + GetLastErrorAsString(GetLastError());
|
||||
}
|
||||
|
||||
|
||||
// Get display name.
|
||||
std::unique_ptr<TCHAR[]> display_name_buffer( new TCHAR[sze] );
|
||||
if (::CertGetNameString( context, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, display_name_buffer.get(), sze) <= 1)
|
||||
std::unique_ptr<TCHAR[]> display_name_buffer(new TCHAR[sze]);
|
||||
if (::CertGetNameString(context, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, display_name_buffer.get(), sze) <= 1)
|
||||
{
|
||||
throw wstring( TEXT("Cannot get certificate info.")) + GetLastErrorAsString(GetLastError());
|
||||
throw wstring(TEXT("Cannot get certificate info.")) + GetLastErrorAsString(GetLastError());
|
||||
}
|
||||
display_name = display_name_buffer.get();
|
||||
|
||||
} catch (const wstring& s) {
|
||||
if (displayErrorMessage)
|
||||
}
|
||||
catch (const wstring& s) {
|
||||
if (module2check == nm_scilexer)
|
||||
::MessageBox(NULL, s.c_str(), TEXT("DLL signature verification failed"), MB_ICONERROR);
|
||||
OutputDebugString(TEXT("VerifyLibrary: error while getting certificate informations\n"));
|
||||
status = false;
|
||||
} catch (...) {
|
||||
}
|
||||
catch (...) {
|
||||
// Unknown error
|
||||
OutputDebugString(TEXT("VerifyLibrary: error while getting certificate informations\n"));
|
||||
if (displayErrorMessage)
|
||||
if (module2check == nm_scilexer)
|
||||
{
|
||||
wstring errMsg(TEXT("Unknown exception occurred. "));
|
||||
errMsg += GetLastErrorAsString(GetLastError());
|
||||
@ -248,31 +295,29 @@ bool VerifySignedLibrary(const wstring& filepath,
|
||||
//
|
||||
// fields verifications - if status is true, and string to compare (from the parameter) is not empty, then do compare
|
||||
//
|
||||
if ( status && !cert_display_name.empty() && cert_display_name != display_name )
|
||||
if (status && _signer_display_name != display_name)
|
||||
{
|
||||
status = false;
|
||||
OutputDebugString(TEXT("VerifyLibrary: Invalid certificate display name\n"));
|
||||
}
|
||||
|
||||
if ( status && !cert_subject.empty() && cert_subject != subject)
|
||||
if (status && _signer_subject != subject)
|
||||
{
|
||||
status = false;
|
||||
OutputDebugString(TEXT("VerifyLibrary: Invalid certificate subject\n"));
|
||||
}
|
||||
|
||||
if ( status && !cert_key_id_hex.empty() && cert_key_id_hex != key_id_hex )
|
||||
if (status && _signer_key_id != key_id_hex)
|
||||
{
|
||||
status = false;
|
||||
OutputDebugString(TEXT("VerifyLibrary: Invalid certificate key id\n"));
|
||||
}
|
||||
|
||||
// Clean up.
|
||||
|
||||
|
||||
if (hStore != NULL) CertCloseStore(hStore, 0);
|
||||
if (hMsg != NULL) CryptMsgClose(hMsg);
|
||||
if (hMsg != NULL) CryptMsgClose(hMsg);
|
||||
if (pSignerInfo != NULL) LocalFree(pSignerInfo);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
#undef VerifySignedLibrary_DISABLE_REVOCATION_CHECK
|
||||
|
@ -26,9 +26,6 @@
|
||||
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
//#define VerifySignedLibrary_DISABLE_REVOCATION_CHECK "Dont check certificat revocation"
|
||||
|
||||
/*
|
||||
@ -57,17 +54,36 @@
|
||||
* state of the certificates will *not* be checked.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#define NPP_COMPONENT_SIGNER_DISPLAY_NAME TEXT("Notepad++")
|
||||
#define NPP_COMPONENT_SIGNER_SUBJECT TEXT("C=FR, S=Ile-de-France, L=Saint Cloud, O=\"Notepad++\", CN=\"Notepad++\"")
|
||||
#define NPP_COMPONENT_SIGNER_KEY_ID TEXT("42C4C5846BB675C74E2B2C90C69AB44366401093")
|
||||
enum SecurityMode { sm_certif = 0, sm_sha256 = 1 };
|
||||
enum NppModule { nm_scilexer = 0, nm_gup = 1, nm_pluginList = 2 };
|
||||
|
||||
class SecurityGard final
|
||||
{
|
||||
public:
|
||||
SecurityGard();
|
||||
bool checkModule(std::wstring filePath, NppModule module2check);
|
||||
|
||||
private:
|
||||
// SHA256
|
||||
static SecurityMode _securityMode;
|
||||
std::vector<std::wstring> _scilexerSha256;
|
||||
std::vector<std::wstring> _gupSha256;
|
||||
std::vector<std::wstring> _pluginListSha256;
|
||||
|
||||
bool checkSha256(std::wstring filePath, NppModule module2check);
|
||||
|
||||
// Code signing certificate
|
||||
std::wstring _signer_display_name = TEXT("Notepad++");
|
||||
std::wstring _signer_subject = TEXT("C=FR, S=Ile-de-France, L=Saint Cloud, O=\"Notepad++\", CN=\"Notepad++\"");
|
||||
std::wstring _signer_key_id = TEXT("42C4C5846BB675C74E2B2C90C69AB44366401093");
|
||||
bool _doCheckRevocation = false;
|
||||
bool _doCheckChainOfTrust = false;
|
||||
|
||||
bool verifySignedLibrary(const std::wstring& filepath, NppModule module2check);
|
||||
};
|
||||
|
||||
bool VerifySignedLibrary(const std::wstring& filepath,
|
||||
const std::wstring& key_id_hex,
|
||||
const std::wstring& cert_subject,
|
||||
const std::wstring& display_name,
|
||||
bool doCheckRevocation,
|
||||
bool doCheckChainOfTrust,
|
||||
bool displayErrorMessage = true);
|
||||
|
@ -2823,7 +2823,8 @@ void Notepad_plus::command(int id)
|
||||
bool isCertifVerified = true;
|
||||
#else //RELEASE
|
||||
// check the signature on updater
|
||||
bool isCertifVerified = VerifySignedLibrary(updaterFullPath.c_str(), NPP_COMPONENT_SIGNER_KEY_ID, NPP_COMPONENT_SIGNER_SUBJECT, NPP_COMPONENT_SIGNER_DISPLAY_NAME, false, false, false);
|
||||
SecurityGard securityGard;
|
||||
bool isCertifVerified = securityGard.checkModule(updaterFullPath, nm_gup);
|
||||
#endif
|
||||
if (isCertifVerified)
|
||||
{
|
||||
|
@ -206,12 +206,13 @@ HMODULE loadSciLexerDll()
|
||||
// This is helpful for developers to skip signature checking
|
||||
// while analyzing issue or modifying the lexer dll
|
||||
#ifndef _DEBUG
|
||||
bool isOK = VerifySignedLibrary(sciLexerPath, NPP_COMPONENT_SIGNER_KEY_ID, NPP_COMPONENT_SIGNER_SUBJECT, NPP_COMPONENT_SIGNER_DISPLAY_NAME, false, false);
|
||||
SecurityGard securityGard;
|
||||
bool isOK = securityGard.checkModule(sciLexerPath, nm_scilexer);
|
||||
|
||||
if (!isOK)
|
||||
{
|
||||
::MessageBox(NULL,
|
||||
TEXT("Authenticode check failed: signature or signing certificate are not recognized"),
|
||||
TEXT("Authenticode check failed:\rsigning certificate or hash is not recognized"),
|
||||
TEXT("Library verification failed"),
|
||||
MB_OK | MB_ICONERROR);
|
||||
return nullptr;
|
||||
|
@ -714,11 +714,13 @@ bool PluginsAdminDlg::isValide()
|
||||
|
||||
// check the signature on default location : %APPDATA%\Notepad++\plugins\config\pl\nppPluginList.dll or NPP_INST_DIR\plugins\config\pl\nppPluginList.dll
|
||||
|
||||
bool isOK = VerifySignedLibrary(_pluginListFullPath.c_str(), NPP_COMPONENT_SIGNER_KEY_ID, NPP_COMPONENT_SIGNER_SUBJECT, NPP_COMPONENT_SIGNER_DISPLAY_NAME, false, false, false);
|
||||
SecurityGard securityGard;
|
||||
bool isOK = securityGard.checkModule(_pluginListFullPath, nm_pluginList);
|
||||
|
||||
if (!isOK)
|
||||
return isOK;
|
||||
|
||||
isOK = VerifySignedLibrary(_updaterFullPath.c_str(), NPP_COMPONENT_SIGNER_KEY_ID, NPP_COMPONENT_SIGNER_SUBJECT, NPP_COMPONENT_SIGNER_DISPLAY_NAME, false, false, false);
|
||||
isOK = securityGard.checkModule(_updaterFullPath, nm_gup);
|
||||
return isOK;
|
||||
#endif
|
||||
}
|
||||
|
@ -494,7 +494,8 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int)
|
||||
winVer ver = pNppParameters->getWinVersion();
|
||||
bool isGtXP = ver > WV_XP;
|
||||
|
||||
bool isSignatureOK = VerifySignedLibrary(updaterFullPath.c_str(), NPP_COMPONENT_SIGNER_KEY_ID, NPP_COMPONENT_SIGNER_SUBJECT, NPP_COMPONENT_SIGNER_DISPLAY_NAME, false, false, false);
|
||||
SecurityGard securityGard;
|
||||
bool isSignatureOK = securityGard.checkModule(updaterFullPath, nm_gup);
|
||||
|
||||
if (TheFirstOne && isUpExist && doUpdate && isGtXP && isSignatureOK)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user