I need a function in Delphi to verify the digital signature of an external EXE or DLL. In my particular application, I am going to occasionally invoke other processes, but for security purposes I want to make sure these executables were created by our organization before running them.
I have seen Microsoft's example in C, however, I do not want to waste the time translating this to Delphi if somebody else already has.
I would prefer a snippet or code example over a third-party library. Thanks.
Check the signature on an EXE or MSI fileRight-click the EXE or MSI file and select Properties. Click the Digital Signatures tab to check the signature.
When a . dll and/or .exe file is digitally signed by a signer, you can confirm the same from the said file's properties. To detect whether the assembly file is signed or not, right click on the file and click the 'Properties' from the context menu.
Step 1: Right-click on the program that you want to check and select properties from the context menu that is displayed. Step 2: Select the Digital Signatures tab in the Properties window. Step 3: If you see signatures listed on the tab, you know that the file has been signed digitally.
Here you go:
// IsCodeSigned, which verifies that the exe hasn't been modified, uses // WinVerifyTrust, so it's NT only. IsCompanySigningCertificate works on Win9x, // but it only checks that the signing certificate hasn't been replaced, which // keeps someone from re-signing a modified executable. // Imagehlp.dll const CERT_SECTION_TYPE_ANY = $FF; // Any Certificate type function ImageEnumerateCertificates(FileHandle: THandle; TypeFilter: WORD; out CertificateCount: DWORD; Indicies: PDWORD; IndexCount: Integer): BOOL; stdcall; external 'Imagehlp.dll'; function ImageGetCertificateHeader(FileHandle: THandle; CertificateIndex: Integer; var CertificateHeader: TWinCertificate): BOOL; stdcall; external 'Imagehlp.dll'; function ImageGetCertificateData(FileHandle: THandle; CertificateIndex: Integer; Certificate: PWinCertificate; var RequiredLength: DWORD): BOOL; stdcall; external 'Imagehlp.dll'; // Crypt32.dll const CERT_NAME_SIMPLE_DISPLAY_TYPE = 4; PKCS_7_ASN_ENCODING = $00010000; X509_ASN_ENCODING = $00000001; type PCCERT_CONTEXT = type Pointer; HCRYPTPROV_LEGACY = type Pointer; PFN_CRYPT_GET_SIGNER_CERTIFICATE = type Pointer; CRYPT_VERIFY_MESSAGE_PARA = record cbSize: DWORD; dwMsgAndCertEncodingType: DWORD; hCryptProv: HCRYPTPROV_LEGACY; pfnGetSignerCertificate: PFN_CRYPT_GET_SIGNER_CERTIFICATE; pvGetArg: Pointer; end; function CryptVerifyMessageSignature(const pVerifyPara: CRYPT_VERIFY_MESSAGE_PARA; dwSignerIndex: DWORD; pbSignedBlob: PByte; cbSignedBlob: DWORD; pbDecoded: PBYTE; pcbDecoded: PDWORD; ppSignerCert: PCCERT_CONTEXT): BOOL; stdcall; external 'Crypt32.dll'; function CertGetNameStringA(pCertContext: PCCERT_CONTEXT; dwType: DWORD; dwFlags: DWORD; pvTypePara: Pointer; pszNameString: PAnsiChar; cchNameString: DWORD): DWORD; stdcall; external 'Crypt32.dll'; function CertFreeCertificateContext(pCertContext: PCCERT_CONTEXT): BOOL; stdcall; external 'Crypt32.dll'; function CertCreateCertificateContext(dwCertEncodingType: DWORD; pbCertEncoded: PBYTE; cbCertEncoded: DWORD): PCCERT_CONTEXT; stdcall; external 'Crypt32.dll'; // WinTrust.dll const WINTRUST_ACTION_GENERIC_VERIFY_V2: TGUID = '{00AAC56B-CD44-11d0-8CC2-00C04FC295EE}'; WTD_CHOICE_FILE = 1; WTD_REVOKE_NONE = 0; WTD_UI_NONE = 2; type PWinTrustFileInfo = ^TWinTrustFileInfo; TWinTrustFileInfo = record cbStruct: DWORD; // = sizeof(WINTRUST_FILE_INFO) pcwszFilePath: PWideChar; // required, file name to be verified hFile: THandle; // optional, open handle to pcwszFilePath pgKnownSubject: PGUID; // optional: fill if the subject type is known end; PWinTrustData = ^TWinTrustData; TWinTrustData = record cbStruct: DWORD; pPolicyCallbackData: Pointer; pSIPClientData: Pointer; dwUIChoice: DWORD; fdwRevocationChecks: DWORD; dwUnionChoice: DWORD; pFile: PWinTrustFileInfo; dwStateAction: DWORD; hWVTStateData: THandle; pwszURLReference: PWideChar; dwProvFlags: DWORD; dwUIContext: DWORD; end; function WinVerifyTrust(hwnd: HWND; const ActionID: TGUID; ActionData: Pointer): Longint; stdcall; external wintrust; {-----------------------------------------------} function IsCodeSigned(const Filename: string): Boolean; var file_info: TWinTrustFileInfo; trust_data: TWinTrustData; begin // Verify that the exe is signed and the checksum matches FillChar(file_info, SizeOf(file_info), 0); file_info.cbStruct := sizeof(file_info); file_info.pcwszFilePath := PWideChar(WideString(Filename)); FillChar(trust_data, SizeOf(trust_data), 0); trust_data.cbStruct := sizeof(trust_data); trust_data.dwUIChoice := WTD_UI_NONE; trust_data.fdwRevocationChecks := WTD_REVOKE_NONE; trust_data.dwUnionChoice := WTD_CHOICE_FILE; trust_data.pFile := @file_info; Result := WinVerifyTrust(INVALID_HANDLE_VALUE, WINTRUST_ACTION_GENERIC_VERIFY_V2, @trust_data) = ERROR_SUCCESS end; {-----------------------------------------------} function IsCompanySigningCertificate(const Filename, CompanyName :string): Boolean; var hExe: HMODULE; Cert: PWinCertificate; CertContext: PCCERT_CONTEXT; CertCount: DWORD; CertName: AnsiString; CertNameLen: DWORD; VerifyParams: CRYPT_VERIFY_MESSAGE_PARA; begin // Returns TRUE if the SubjectName on the certificate used to sign the exe is // "Company Name". Should prevent a cracker from modifying the file and // re-signing it with their own certificate. // // Microsoft has an example that does this using CryptQueryObject and // CertFindCertificateInStore instead of CryptVerifyMessageSignature, but // CryptQueryObject is NT-only. Using CertCreateCertificateContext doesn't work // either, though I don't know why. Result := False; // Verify that the exe was signed by our private key hExe := CreateFile(PChar(Filename), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL or FILE_FLAG_RANDOM_ACCESS, 0); if hExe = INVALID_HANDLE_VALUE then Exit; try // There should only be one certificate associated with the exe if (not ImageEnumerateCertificates(hExe, CERT_SECTION_TYPE_ANY, CertCount, nil, 0)) or (CertCount <> 1) then Exit; // Read the certificate header so we can get the size needed for the full cert GetMem(Cert, SizeOf(TWinCertificate) + 3); // ImageGetCertificateHeader writes an DWORD at bCertificate for some reason try Cert.dwLength := 0; Cert.wRevision := WIN_CERT_REVISION_1_0; if not ImageGetCertificateHeader(hExe, 0, Cert^) then Exit; // Read the full certificate ReallocMem(Cert, SizeOf(TWinCertificate) + Cert.dwLength); if not ImageGetCertificateData(hExe, 0, Cert, Cert.dwLength) then Exit; // Get the certificate context. CryptVerifyMessageSignature has the // side effect of creating a context for the signing certificate. FillChar(VerifyParams, SizeOf(VerifyParams), 0); VerifyParams.cbSize := SizeOf(VerifyParams); VerifyParams.dwMsgAndCertEncodingType := X509_ASN_ENCODING or PKCS_7_ASN_ENCODING; if not CryptVerifyMessageSignature(VerifyParams, 0, @Cert.bCertificate, Cert.dwLength, nil, nil, @CertContext) then Exit; try // Extract and compare the certificate's subject names. Don't // compare the entire certificate or the public key as those will // change when the certificate is renewed. CertNameLen := CertGetNameStringA(CertContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, nil, nil, 0); SetLength(CertName, CertNameLen - 1); CertGetNameStringA(CertContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, nil, PAnsiChar(CertName), CertNameLen); if CertName <> CompanyName then Exit; finally CertFreeCertificateContext(CertContext) end; finally FreeMem(Cert); end; finally CloseHandle(hExe); end; Result := True; end;
const WTD_UI_ALL = 1; WTD_UI_NONE = 2; WTD_UI_NOBAD = 3; WTD_UI_NOGOOD = 4; WTD_REVOKE_NONE = $00000000; WTD_REVOKE_WHOLECHAIN = $00000001; WTD_CHOICE_FILE = 1; WTD_CHOICE_CATALOG = 2; WTD_CHOICE_BLOB = 3; WTD_CHOICE_SIGNER = 4; WTD_CHOICE_CERT = 5; WTD_STATEACTION_IGNORE = $00000000; WTD_STATEACTION_VERIFY = $00000001; WTD_STATEACTION_CLOSE = $00000002; WTD_STATEACTION_AUTO_CACHE = $00000003; WTD_STATEACTION_AUTO_CACHE_FLUSH = $00000004; type PWinTrustFileInfo = ^TWinTrustFileInfo; TWinTrustFileInfo = record cbStruct: DWORD; pcwszFilePath: PWideChar; hFile: THandle; pgKnownSubject: PGUID; end; PWinTrustData = ^TWinTrustData; TWinTrustData = record cbStruct: DWORD; pPolicyCallbackData: Pointer; pSIPClientData: Pointer; dwUIChoice: DWORD; fdwRevocationChecks: DWORD; dwUnionChoice: DWORD; pUnionData: Pointer; dwStateAction: DWORD; hWVTStateData: THandle; pwszURLReference: PWideChar; dwProvFlags: DWORD; dwUIContext: DWORD; end; function VerifySignature(const FileName: WideString): Longint; var FileInfo: TWinTrustFileInfo; TrustData: TWinTrustData; begin FillChar(FileInfo, SizeOf(FileInfo), 0); FileInfo.cbStruct := SizeOf(FileInfo); FileInfo.pcwszFilePath := PWideChar(FileName); FillChar(TrustData, SizeOf(TrustData), 0); TrustData.cbStruct := SizeOf(TrustData); TrustData.dwUIChoice := WTD_UI_NONE; TrustData.fdwRevocationChecks := WTD_REVOKE_NONE; TrustData.dwUnionChoice := WTD_CHOICE_FILE; TrustData.pUnionData := @FileInfo; TrustData.dwStateAction := WTD_STATEACTION_IGNORE; TrustData.dwProvFlags := WTD_SAFER_FLAG; TrustData.dwUIContext := WTD_UICONTEXT_EXECUTE; Result := WinVerifyTrust(0, WINTRUST_ACTION_GENERIC_VERIFY_V2, @TrustData); end;
There are more details in the documentation.
Alternatively, you can use CAPICOM. Import the CAPICOM type library from capicom.dll and then use the generated CAPICOM_TLB unit:
procedure CodeSignVerify(const FileName: string; AllowUserPrompt: Boolean = False); var SignedCode: ISignedCode; begin SignedCode := CoSignedCode.Create; SignedCode.FileName := FileName; SignedCode.Verify(AllowUserPrompt); end;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With