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

寻工具无果

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

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

自己动手丰衣足食

注意:

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

 2. 必须先通过 CryptSIPRetrieveSubjectGuid 根据文件类型获取相应的算法接口GUID,再用CryptSIPLoad 载入算法提供者。虽然直接使用“通用”的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

提示

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

代码

{
  * 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.