一個客戶想通過編程實現驗證程序自身的數字簽名來確保程序的完整性,防范病毒感染以及防止一些無聊人士的修改(通過十六進制編輯器替換一些版權、網址、LOGO..); 為此我做了一個數字簽名驗證的小例子,其中也有獲取簽名者信息的方法,以滿足“自驗證”的需求。
示例:
WinAPI:
• 安全編錄(CAT)
CryptCATAdminReleaseCatalogContext
CryptCATCatalogInfoFromContext
CryptCATAdminEnumCatalogFromHash
CryptCATAdminCalcHashFromFileHandle
CryptCATAdminReleaseContext
CryptCATAdminAcquireContext
• 驗證文件的簽名(主API)
WinVerifyTrust
• 獲取簽名信息
WTHelperProvDataFromStateData
• 獲取證書名字信息
CertGetNameString
代碼:
{
* by: HouSoft
* site: www.yryz.net
* created: 2012/02/03
}
unit Unit1;
interface
uses
Windows, Sysutils, jwaWinCrypt, WinTrustApi;
procedure Test;
implementation
procedure PrintCertChain(pCertChain: PCERT_SIMPLE_CHAIN);
var
I: Integer;
sBuf: string;
begin
// 開啟指針運算
{$POINTERMATH ON}
//
// 輸出書鏈元素
for I := pCertChain^.cElement - 1 downto 0 do
begin
SetLength(sBuf, 1024);
SetLength(sBuf,
CertGetNameString(
pCertChain^.rgpElement[I].pCertContext,
CERT_NAME_SIMPLE_DISPLAY_TYPE, // 簡單名字
0,
nil,
PChar(sBuf),
Length(sBuf)) - 1);
WriteLn(#9, StringOfChar(' ', 2 * (pCertChain^.cElement - I - 1)), sBuf);
end;
end;
procedure OutSignerInfo(hWVTStateData: THANDLE);
var
provData: PCRYPT_PROVIDER_DATA;
LSysTime: TSystemTime;
begin
// 獲取簽名信息
// http://msdn.microsoft.com/ZH-CN/library/windows/desktop/aa388429(v=vs.85).aspx
provData := WTHelperProvDataFromStateData(hWVTStateData);
if (provData <> nil) and (provData^.pasSigners <> nil) then
begin
// 采用安全編錄(CAT)簽名
if provData^.pPDSip^.psSipCATSubjectInfo <> nil then
begin
WriteLn('安全編錄: ');
WriteLn(#9, provData^.pPDSip^.psSipCATSubjectInfo^.pwsFileName);
WriteLn('');
end;
/// 注意: provData^.pasSigners 是數組, 但常見的都是一個元素,so...
// 時間戳
if provData^.pasSigners^.pasCounterSigners <> nil then
begin
FileTimeToSystemTime(provData^.pasSigners^.pasCounterSigners^.sftVerifyAsOf, LSysTime);
WriteLn('時間戳: ');
WriteLn(#9, FormatDateTime('yyyy-MM-dd hh:mm:ss', SystemTimeToDateTime(LSysTime)));
WriteLn('');
WriteLn('時間戳證書鏈: ');
PrintCertChain(provData^.pasSigners^.pasCounterSigners^.pChainContext^.rgpChain[0]);
WriteLn('');
end;
WriteLn('簽名者證書鏈:');
PrintCertChain(provData^.pasSigners^.pChainContext^.rgpChain[0]);
WriteLn('');
end;
end;
function SignVerify(FileName: string): Boolean;
var
aByteHash: array [0 .. 255] of Byte;
iByteCount: Integer;
hCatAdminContext: HCatAdmin;
WTrustData: WINTRUST_DATA;
WTDCatalogInfo: WINTRUST_CATALOG_INFO;
WTDFileInfo: WINTRUST_FILE_INFO;
CatalogInfo: CATALOG_INFO;
hFile: THANDLE;
hCatalogContext: THANDLE;
swFilename: WideString;
swMemberTag: WideString;
ilRet: Longint;
I: Integer;
begin
Result := False;
if not FileExists(FileName) then
Exit;
swFilename := FileName;
ZeroMemory(@CatalogInfo, SizeOf(CatalogInfo));
ZeroMemory(@WTDFileInfo, SizeOf(WTDFileInfo));
ZeroMemory(@WTDCatalogInfo, SizeOf(WTDCatalogInfo));
ZeroMemory(@WTrustData, SizeOf(WTrustData));
hCatalogContext := 0;
hCatAdminContext := 0;
try
// 先查詢安全編目
if not CryptCATAdminAcquireContext(@hCatAdminContext,
nil,
0) then
Exit;
hFile := CreateFile(PChar(FileName),
GENERIC_READ,
FILE_SHARE_READ,
nil,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0);
if hFile = INVALID_HANDLE_VALUE then
Exit;
iByteCount := SizeOf(aByteHash);
// 文件哈希函數計算的
CryptCATAdminCalcHashFromFileHandle(hFile,
@iByteCount,
@aByteHash,
0);
for i := 0 to iByteCount - 1 do
begin
swMemberTag := swMemberTag + IntToHex(aByteHash[i], 2);
end;
CloseHandle(hFile);
// 枚舉目錄包含一個指定的哈希
hCatalogContext := CryptCATAdminEnumCatalogFromHash(hCatAdminContext,
@aByteHash,
iByteCount,
0,
nil);
// 准備驗證參數
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa388205(v=vs.85).aspx
WTrustData.dwUIChoice := WTD_UI_NONE;
WTrustData.fdwRevocationChecks := WTD_REVOKE_NONE;
WTrustData.dwStateAction := WTD_STATEACTION_VERIFY; // 獲取信息後需要手動 WTD_STATEACTION_CLOSE
WTrustData.dwProvFlags := WTD_REVOCATION_CHECK_NONE;
if hCatalogContext = 0 then // 未找到包含此文件的安全編目
begin
WTDFileInfo.cbStruct := SizeOf(WTDFileInfo);
WTDFileInfo.pcwszFilePath := PWideChar(swFilename);
WTrustData.cbStruct := SizeOf(WTrustData);
WTrustData.dwUnionChoice := WTD_CHOICE_FILE;
WTrustData.union.pFile := @WTDFileInfo;
end
else
begin
CryptCATCatalogInfoFromContext(hCatalogContext, @CatalogInfo, 0);
WTDCatalogInfo.cbStruct := SizeOf(WTDCatalogInfo);
WTDCatalogInfo.pcwszCatalogFilePath := CatalogInfo.sCatalogFile;
WTDCatalogInfo.pcwszMemberFilePath := PWideChar(swFilename);
WTDCatalogInfo.pcwszMemberTag := PWideChar(swMemberTag);
WTrustData.cbStruct := SizeOf(WTrustData);
WTrustData.dwUnionChoice := WTD_CHOICE_CATALOG;
WTrustData.union.pCatalog := @WTDCatalogInfo;
// WriteLn(CatalogInfo.sCatalogFile);
end;
// 驗證
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa388208(v=vs.85).aspx
ilRet := WinVerifyTrust(INVALID_HANDLE_VALUE,
@WINTRUST_ACTION_GENERIC_VERIFY_V2,
@WTrustData);
Result := ilRet = 0;
// 輸出簽名信息
OutSignerInfo(WTrustData.hWVTStateData);
// 釋放
WTrustData.dwStateAction := WTD_STATEACTION_CLOSE;
WinVerifyTrust(INVALID_HANDLE_VALUE,
@WINTRUST_ACTION_GENERIC_VERIFY_V2,
@WTrustData);
finally
if hCatAdminContext > 0 then
begin
if hCatalogContext > 0 then
CryptCATAdminReleaseCatalogContext(hCatAdminContext,
hCatalogContext, 0);
CryptCATAdminReleaseContext(hCatAdminContext, 0);
end;
end;
end;
procedure Test;
begin
if ParamCount < 1 then
begin
WriteLn('請輸入要驗證的文件名!');
Exit;
end;
if SignVerify(ParamStr(1)) then
WriteLn('簽名有效.')
else
WriteLn('簽名無效.');
end;
end.
摘自 一人游走