Checking digital signature programmatically from Delphi
12,511
Solution 1
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;
Solution 2
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;
Author by
kes
Updated on June 27, 2022Comments
-
kes almost 2 years
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.
-
kes about 13 yearsThanks, but this appears to operate on the current executable, rather than an external exe specified by filename. Sorry, I probably should have been more clear. Would it be a lot of work to alter this snippet to function in such a way?
-
med about 13 yearsscrapdog: Just replace ParamStr(0) (appears twice) with the name of the file you want to check.
-
Zoë Peterson about 13 years@scrapdog: Make sure you call both functions if you're running on NT. IsCompanySigningCertificate works on both 9x and NT, but only verifies that the exe has your certificate on it. IsCodeSigned actually verifies that file hasn't been tampered with. I updated IsCodeSigned to take a filename as well.
-
Jasper Schellingerhout over 8 years
WTD_SAFER_FLAG
is \ has been marked as "not supported". -
delphirules almost 4 yearsIs there a simpler way just to check if the file is signed or not ? I don't need the signature details. just know if it's signed.