MakeCat Delphi实现生成安全编录(用于数字签名)

寻工具无果

微软出品的安全编录(cat)生成工具不太灵活,有些需求无法较好的满足。

  1. 无法对包含中文文件名的文件进行操作
  2. 不能同时对多个目录中的文件进行操作(特别是32bit & 64bit 文件名相同时),必须分别生成后,再进行签名。如 i386、amd64

自己动手丰衣足食

注意:

 1. 目前貌似微软的API对cat文件生成cat有BUG; 强制使用CRYPT_SUBJTYPE_FLAT_IMAGE好像可以… 待验证

 2. 必须先通过 CryptSIPRetrieveSubjectGuid.aspx) 根据文件类型获取相应的算法接口GUID,再用CryptSIPLoad.aspx) 载入算法提供者。虽然直接使用“通用”的GUID: CRYPT_SUBJTYPE_FLAT_IMAGE 可以成功生成Cat,但在验证签名时PE、CAB等专有格式会无效! 如:

WinAPI:

  1. CryptCATOpen
  2. CryptCATPutMemberInfo
  3. CryptSIPRetrieveSubjectGuid
  4. CryptSIPLoad
  5. CryptCATPutMemberInfo
  6. CryptCATPutAttrInfo
  7. CryptCATPersistStore
  8. CryptCATClose

提示

此功能已集成于“数字签名工具”中。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
{
* 2011/12/14 Created by Hou
* Site: www.yryz.net
}
unit MakeCat_u;

interface

uses
Windows,
SysUtils,
jwaWinCrypt,
MssipApi,
MsCatApi;

procedure Test;

implementation

const
// 根据Win2k泄漏的部分源码 mscdfapi.cpp + OllyICE makecat.exe获得(2011/12/20 by Hou)
CRYPT_SUBJTYPE_FLAT_IMAGE: TGUID = '{DE351A42-8E59-11D0-8C47-00C04FC295EE}';

function BytesToHex(const Bytes; Len: Integer): string;
var
I: Integer;
begin
Result := '';
for I := 0 to Len - 1 do
Result := Result + IntToHex(PByteArray(@Bytes)^[I], 2);
end;

// 计算间接数据(HASH)
function CalcIndirectData(hCat: THandle; pgSubjectType: PGUID; FileName: string;
var dwCertVersion: DWORD): TBytes;
var
SubjInfo: SIP_SUBJECTINFO;
DispInfo: SIP_DISPATCH_INFO;
LDataLen: Integer;
begin
ZeroMemory(@SubjInfo, SizeOf(SubjInfo));
SubjInfo.cbSize := SizeOf(SubjInfo);
SubjInfo.hProv := PCRYPTCATSTORE(hCat)^.hProv;
SubjInfo.DigestAlgorithm.pszObjId := CertAlgIdToOID(CALG_SHA1); // 摘要算法
SubjInfo.dwFlags := SPC_INC_PE_RESOURCES_FLAG
or SPC_INC_PE_IMPORT_ADDR_TABLE_FLAG
or MSSIP_FLAGS_PROHIBIT_RESIZE_ON_CREATE;
SubjInfo.dwEncodingType := PCRYPTCATSTORE(hCat)^.dwEncodingType;

SubjInfo.pgSubjectType := pgSubjectType;
SubjInfo.pwsFileName := PWChar(WideString(FileName));

// 载入主体接口套件, 主体类型可以是exe、cab、cat、flat
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms721625(v=VS.85).aspx#_security_subject_interface_package_gly
if not CryptSIPLoad(pgSubjectType,
0,
@DispInfo) then
raise Exception.CreateFmt('CryptSIPLoad Fail(%d:%s)',
[GetLastError, SysErrorMessage(GetLastError)]);

if not DispInfo.pfCreate(@SubjInfo,
@LDataLen,
nil) and (LDataLen < 1) then
raise Exception.CreateFmt('DispInfo.pfCreate Fail(%d:%s)',
[GetLastError, SysErrorMessage(GetLastError)]);

SetLength(Result, LDataLen);

if not DispInfo.pfCreate(@SubjInfo,
@LDataLen,
PSIP_INDIRECT_DATA(Result)) then
raise Exception.CreateFmt('DispInfo.pfCreate Fail2(%d:%s)',
[GetLastError, SysErrorMessage(GetLastError)]);

dwCertVersion := SubjInfo.dwIntVersion;
end;

procedure Test;
var
hCat: THandle;
IndirectData: TBytes;
pMember: PCRYPTCATMEMBER;
FileName: string;
SubjectGuid: TGUID;
dwCertVersion: DWORD;
pwszFileName: LPWSTR;
pwszReferenceTag: LPWSTR;
begin
if ParamCount < 1 then
WriteLn('Command: App ');

FileName := ParamStr(ParamCount);

try
// 创建CAT
hCat := CryptCATOpen('test.cat',
CRYPTCAT_OPEN_CREATENEW,
0,
$00000001,
PKCS_7_ASN_ENCODING or X509_ASN_ENCODING);

if hCat = INVALID_HANDLE_VALUE then
raise Exception.CreateFmt('CryptCATOpen Fail(%d:%s)',
[GetLastError, SysErrorMessage(GetLastError)]);

// 根据文件类型获取Subject GUID
// !必须这样,SignTool验证时是根据文件类型采用相应的算法接口
if not CryptSIPRetrieveSubjectGuid(PWChar(WideString(FileName)),
0,
@SubjectGuid) then
SubjectGuid := CRYPT_SUBJTYPE_FLAT_IMAGE;

WriteLn('SubjectGuid: ' + GUIDToString(SubjectGuid));

// 计算间接数据(HASH)
IndirectData := CalcIndirectData(hCat,
@SubjectGuid,
FileName,
dwCertVersion);

// WriteLn(PSIP_INDIRECT_DATA(IndirectData)^.Data.pszObjId);

pwszFileName := PWChar(WideString(ExtractFileName(FileName)));
pwszReferenceTag := PWChar(WideString(BytesToHex(PSIP_INDIRECT_DATA(IndirectData)^.Digest.pbData^,
PSIP_INDIRECT_DATA(IndirectData)^.Digest.cbData))); // Hash

// 添加成员
// http://msdn.microsoft.com/en-us/library/windows/desktop/bb736352(v=vs.85).aspx
pMember := CryptCATPutMemberInfo(hCat,
pwszFileName,
pwszReferenceTag,
@CRYPT_SUBJTYPE_FLAT_IMAGE,
dwCertVersion,
Length(IndirectData),
PByte(IndirectData));

// 成员属性
// http://msdn.microsoft.com/en-us/library/bb736351(d=lightweight,l=en-us,v=VS.85).aspx
if Assigned(pMember) then
CryptCATPutAttrInfo(hCat,
pMember,
PWChar('File'),
CRYPTCAT_ATTR_AUTHENTICATED or CRYPTCAT_ATTR_DATAASCII or CRYPTCAT_ATTR_NAMEASCII,
(Length(pwszFileName) + 1) * SizeOf(WChar),
PByte(pwszFileName));

// 保存(持久化存储,关键!)
if not CryptCATPersistStore(hCat) then
raise Exception.CreateFmt('CryptCATPersistStore Fail(%d:%s)',
[GetLastError, SysErrorMessage(GetLastError)]);

// 关闭CAT
CryptCATClose(hCat);
except
on E: Exception do
MessageBox(GetActiveWindow, PChar(E.Message), '异常', 0);
end;
end;

end.