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.
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;