From 6b0a3c8e6b03453006ad94ae8db918f5f56a31a9 Mon Sep 17 00:00:00 2001 From: Don HO Date: Mon, 8 May 2017 00:19:56 +0200 Subject: [PATCH] Enhance certificate check --- PowerEditor/src/MISC/Common/Common.cpp | 4 - .../src/MISC/Common/verifySignedfile.cpp | 259 ++++++++++++++++++ .../src/MISC/Common/verifySignedfile.h | 39 +++ .../ScitillaComponent/ScintillaEditView.cpp | 19 +- .../visual.net/notepadPlus.vs2015.vcxproj | 10 +- 5 files changed, 320 insertions(+), 11 deletions(-) create mode 100644 PowerEditor/src/MISC/Common/verifySignedfile.cpp create mode 100644 PowerEditor/src/MISC/Common/verifySignedfile.h diff --git a/PowerEditor/src/MISC/Common/Common.cpp b/PowerEditor/src/MISC/Common/Common.cpp index aaf09663..f5055253 100644 --- a/PowerEditor/src/MISC/Common/Common.cpp +++ b/PowerEditor/src/MISC/Common/Common.cpp @@ -34,12 +34,8 @@ #include "Common.h" #include "../Utf8.h" - WcharMbcsConvertor* WcharMbcsConvertor::_pSelf = new WcharMbcsConvertor; - - - void printInt(int int2print) { TCHAR str[32]; diff --git a/PowerEditor/src/MISC/Common/verifySignedfile.cpp b/PowerEditor/src/MISC/Common/verifySignedfile.cpp new file mode 100644 index 00000000..5eb2b4ad --- /dev/null +++ b/PowerEditor/src/MISC/Common/verifySignedfile.cpp @@ -0,0 +1,259 @@ +// This file is part of Notepad++ project +// Copyright (C)2003 Don HO +// +// 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. +// +// Note that the GPL places important restrictions on "derived works", yet +// it does not provide a detailed definition of that term. To avoid +// misunderstandings, we consider an application to constitute a +// "derivative work" for the purpose of this license if it does any of the +// following: +// 1. Integrates source code from Notepad++. +// 2. Integrates/includes/aggregates Notepad++ into a proprietary executable +// installer, such as those produced by InstallShield. +// 3. Links to a library or executes a program that does any of the above. +// +// 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; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + +// VerifyDLL.cpp : Verification of an Authenticode signed DLL +// + +#include +#include +#include +#include +#include +#include +#include +#include "VerifySignedFile.h" +#include "Common.h" + +using namespace std; + +bool VerifySignedLibrary(const wstring& filepath, + const wstring& cert_key_id_hex, + const wstring& cert_subject, + const wstring& cert_display_name) +{ + wstring display_name; + wstring key_id_hex; + wstring subject; + + wstring dmsg(TEXT("VerifyLibrary: ")); + dmsg += filepath; + dmsg += TEXT("\n"); + + OutputDebugString(dmsg.c_str()); + + ////////////////////// Signature verification + + // Initialize the WINTRUST_FILE_INFO structure. + LPCWSTR pwszfilepath = filepath.c_str(); + WINTRUST_FILE_INFO file_data = { 0 }; + file_data.cbStruct = sizeof(WINTRUST_FILE_INFO); + file_data.pcwszFilePath = pwszfilepath; + + // Initialise WinTrust data + WINTRUST_DATA winTEXTrust_data = { 0 }; + winTEXTrust_data.cbStruct = sizeof(winTEXTrust_data); + winTEXTrust_data.dwUIChoice = WTD_UI_NONE; // do not display optionnal 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; + +#if defined( VerifySignedLibrary_DISABLE_REVOCATION_CHECK ) + winTEXTrust_data.fdwRevocationChecks = WTD_REVOKE_NONE; + OutputDebugString(TEXT("VerifyLibrary: certificate revocation disabled at compile time\n")); +#else + // if offline, revocation is not checked + // depending of windows version, this may introduce a latency on offline systems + DWORD netstatus; + QOCINFO oci; + oci.dwSize = sizeof(oci); + CONST TCHAR* msftTEXTest_site = TEXT("http://www.msfncsi.com/ncsi.txt"); + bool online = false; + online = (0 != IsNetworkAlive(&netstatus) ); + online = online && ( 0 == GetLastError()); + online = online && (0 == IsDestinationReachable(msftTEXTest_site, &oci)); + if (!online) + { + winTEXTrust_data.fdwRevocationChecks = WTD_REVOKE_NONE; + OutputDebugString(TEXT("VerifyLibrary: system is offline - certificate revocation wont be checked\n")); + } +#endif + + // Verify signature and cert-chain validity + GUID policy = WINTRUST_ACTION_GENERIC_VERIFY_V2; + LONG vtrust = ::WinVerifyTrust(NULL, &policy, &winTEXTrust_data ); + + // Post check cleanup + winTEXTrust_data.dwStateAction = WTD_STATEACTION_CLOSE; + LONG t2 = ::WinVerifyTrust(NULL, &policy, &winTEXTrust_data); + + if (vtrust) + { + OutputDebugString(TEXT("VerifyLibrary: trust verification failed\n")); + return false; + } + + if (t2) + { + OutputDebugString(TEXT("VerifyLibrary: error encountered while cleaning up after WinVerifyTrust\n")); + return false; + } + + ////////////////////// Certificate verification + + HCERTSTORE hStore = nullptr; + HCRYPTMSG hMsg = nullptr; + PCMSG_SIGNER_INFO pSignerInfo = nullptr; + DWORD dwEncoding, dwContentType, dwFormatType; + DWORD dwSignerInfo = 0L; + 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 + ); + if (!result) + { + 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()); + } + + // Get Signer Information. + pSignerInfo = (PCMSG_SIGNER_INFO)LocalAlloc(LPTR, dwSignerInfo); + if (NULL == pSignerInfo ) + { + 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()); + } + + // Get the signer certificate from temporary certificate store. + CERT_INFO cert_info = { 0 }; + 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); + if (!context) + { + throw wstring( TEXT("Certificate context: ")) + GetLastErrorAsString(GetLastError()); + } + + // Getting the full subject + auto subject_sze = ::CertNameToStr(X509_ASN_ENCODING, &context->pCertInfo->Subject, CERT_X500_NAME_STR, NULL, 0); + if (subject_sze <= 1) + { + throw wstring(TEXT("Getting x509 field size problem.")); + } + + std::unique_ptr subject_buffer(new TCHAR[subject_sze]); + if (::CertNameToStr(X509_ASN_ENCODING, &context->pCertInfo->Subject, CERT_X500_NAME_STR, subject_buffer.get(), subject_sze) <= 1) + { + throw wstring(TEXT("Failed to get x509 filed infos from certificate.")); + } + subject = subject_buffer.get(); + + // Getting key_id + DWORD key_id_sze = 0; + if (!::CertGetCertificateContextProperty( context, CERT_KEY_IDENTIFIER_PROP_ID, NULL, &key_id_sze)) + { + throw wstring( TEXT("x509 property not found")) + GetLastErrorAsString(GetLastError()); + } + + std::unique_ptr 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()); + } + + 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]; + } + key_id_hex = ss.str(); + wstring dbg = key_id_hex + TEXT("\n"); + OutputDebugString( dbg.c_str() ); + + // Getting the display name + 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()); + } + + // Get display name. + std::unique_ptr 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()); + } + display_name = display_name_buffer.get(); + + } catch (wstring s) { + ::MessageBox(NULL, TEXT("DLL signature verification failed"), s.c_str(), MB_ICONERROR); + OutputDebugString(TEXT("VerifyLibrary: error while getting certificate informations\n")); + status = false; + } catch (...) { + // Unknown error + OutputDebugString(TEXT("VerifyLibrary: error while getting certificate informations\n")); + throw wstring( TEXT("Unknown exception occured. ")) + GetLastErrorAsString(GetLastError()); + } + + // fields verifications + if ( status && !cert_display_name.empty() && cert_display_name != display_name ) + { + status = false; + OutputDebugString(TEXT("VerifyLibrary: Invalid certificate display name\n")); + } + + if ( status && !cert_subject.empty() && 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 ) + { + status = false; + OutputDebugString(TEXT("VerifyLibrary: Invalid certificate key id\n")); + } + + // Clean up. + + if (hStore != NULL) CertCloseStore(hStore, 0); + if (hMsg != NULL) CryptMsgClose(hMsg); + if (pSignerInfo != NULL) LocalFree(pSignerInfo); + + return status; + + //////////////////////// +} + +#undef VerifySignedLibrary_DISABLE_REVOCATION_CHECK diff --git a/PowerEditor/src/MISC/Common/verifySignedfile.h b/PowerEditor/src/MISC/Common/verifySignedfile.h new file mode 100644 index 00000000..7ae70363 --- /dev/null +++ b/PowerEditor/src/MISC/Common/verifySignedfile.h @@ -0,0 +1,39 @@ + + +#pragma once + +//#define VerifySignedLibrary_DISABLE_REVOCATION_CHECK "Dont check certificat revocation" + +/* +* Verifies an Authenticde DLL signature and ownership +* +* Parameters: +* @param filepath path to the DLL file to examine +* @param cert_display_name if specified, the signing certificate display name to compare to. Ignored if set to "", (weak comparison) +* @param cert_subject if specified, the full signing certificate subject name. Ignored if set to "" (strong comparison) +* @param cert_key_id_hex if specified, the signing certificate key id (fingerprint), Ignored if set to "" (very strong comparison) +* +* @return true if the verification was positive, false if it was negative of encountered some error +* +* Dependencies: +* This function uses 3 APIs: WinTrust, CryptoAPI, SENS API +* It requires to link on : wintrust.lib, crypt32.lib (or crypt64.lib depending on the compilation target) and sensapi.lib +* Those functions are available on Windows starting with Windows-XP +* +* Limitations: +* Certificate revocation checking requires an access to Internet. +* The functions checks for connectivity and will disable revocation checking if the machine is offline or if Microsoft +* connectivity checking site is not reachable (supposely implying we are on an airgapped network). +* Depending on Windows version, this test will be instantaneous (Windows 8 and up) or may take a few seconds. +* This behaviour can be disabled by setting a define at compilation time. +* If macro VerifySignedLibrary_DISABLE_REVOCATION_CHECK is defined, the revocation +* state of the certificates will *not* be checked. +* +*/ + +#include + +bool VerifySignedLibrary(const std::wstring& filepath, + const std::wstring& key_id_hex, + const std::wstring& cert_subject, + const std::wstring& display_name); diff --git a/PowerEditor/src/ScitillaComponent/ScintillaEditView.cpp b/PowerEditor/src/ScitillaComponent/ScintillaEditView.cpp index 19df601a..e620d759 100644 --- a/PowerEditor/src/ScitillaComponent/ScintillaEditView.cpp +++ b/PowerEditor/src/ScitillaComponent/ScintillaEditView.cpp @@ -25,13 +25,13 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - +#include #include #include "ScintillaEditView.h" #include "Parameters.h" #include "Sorters.h" #include "tchar.h" -#include +#include "verifySignedFile.h" using namespace std; @@ -152,6 +152,11 @@ LanguageName ScintillaEditView::langNames[L_EXTERNAL+1] = { //const int MASK_GREEN = 0x00FF00; //const int MASK_BLUE = 0x0000FF; +const generic_string scintilla_signer_display_name = TEXT("Notepad++"); +const generic_string scintilla_signer_subject = TEXT("C=FR, S=Ile-de-France, L=Saint Cloud, O=\"Notepad++\", CN=\"Notepad++\""); +const generic_string scintilla_signer_key_id = TEXT("42C4C5846BB675C74E2B2C90C69AB44366401093"); + + int getNbDigits(int aNum, int base) { int nbChiffre = 1; @@ -175,12 +180,20 @@ int getNbDigits(int aNum, int base) } TCHAR moduleFileName[1024]; + HMODULE loadSciLexerDll() { generic_string sciLexerPath = getSciLexerFullPathName(moduleFileName, 1024); - if (not isCertificateValidated(sciLexerPath, TEXT("Notepad++"))) + if (not VerifySignedLibrary(sciLexerPath, scintilla_signer_key_id, scintilla_signer_subject, scintilla_signer_display_name)) + { + ::MessageBox(NULL, + TEXT("Authenticode check failed: signature or signing certificate are not recognized"), + TEXT("Library verification failed"), + MB_OK | MB_ICONERROR); return nullptr; + } + return ::LoadLibrary(sciLexerPath.c_str()); } diff --git a/PowerEditor/visual.net/notepadPlus.vs2015.vcxproj b/PowerEditor/visual.net/notepadPlus.vs2015.vcxproj index ced027c5..038e8122 100644 --- a/PowerEditor/visual.net/notepadPlus.vs2015.vcxproj +++ b/PowerEditor/visual.net/notepadPlus.vs2015.vcxproj @@ -109,7 +109,7 @@ /fixed:no %(AdditionalOptions) - comctl32.lib;shlwapi.lib;shell32.lib;Oleacc.lib;Dbghelp.lib;Version.lib;Crypt32.lib;%(AdditionalDependencies) + comctl32.lib;shlwapi.lib;shell32.lib;Oleacc.lib;Dbghelp.lib;Version.lib;Crypt32.lib;wintrust.lib;Sensapi.lib;%(AdditionalDependencies) LinkVerboseLib $(OutDir)notepad++.exe 1.0 @@ -146,7 +146,7 @@ /fixed:no %(AdditionalOptions) - comctl32.lib;shlwapi.lib;shell32.lib;Oleacc.lib;Dbghelp.lib;Version.lib;Crypt32.lib;%(AdditionalDependencies) + comctl32.lib;shlwapi.lib;shell32.lib;Oleacc.lib;Dbghelp.lib;Version.lib;Crypt32.lib;wintrust.lib;Sensapi.lib;%(AdditionalDependencies) LinkVerboseLib $(OutDir)notepad++.exe 1.0 @@ -188,7 +188,7 @@ 18 - comctl32.lib;shlwapi.lib;shell32.lib;Oleacc.lib;Dbghelp.lib;Version.lib;Crypt32.lib;%(AdditionalDependencies) + comctl32.lib;shlwapi.lib;shell32.lib;Oleacc.lib;Dbghelp.lib;Version.lib;Crypt32.lib;wintrust.lib;Sensapi.lib;%(AdditionalDependencies) LinkVerboseLib $(OutDir)notepad++.exe 1.0 @@ -239,7 +239,7 @@ copy ..\src\contextMenu.xml ..\bin\contextMenu.xml 18 - comctl32.lib;shlwapi.lib;shell32.lib;Oleacc.lib;Dbghelp.lib;Version.lib;Crypt32.lib;%(AdditionalDependencies) + comctl32.lib;shlwapi.lib;shell32.lib;Oleacc.lib;Dbghelp.lib;Version.lib;Crypt32.lib;wintrust.lib;Sensapi.lib;%(AdditionalDependencies) LinkVerboseLib $(OutDir)notepad++.exe 1.0 @@ -269,6 +269,7 @@ copy ..\src\contextMenu.xml ..\bin64\contextMenu.xml + @@ -534,6 +535,7 @@ copy ..\src\contextMenu.xml ..\bin64\contextMenu.xml +