使用PFX文件创建一个强名称的COM互操作程序集

时间:2011-12-03 05:50:02

标签: com interop cryptography com-interop public-key-encryption

我正在尝试使用PFX文件创建一个强名称的COM互操作程序集 TlbImp.exe对使用“sn.exe -k”(包含公钥和私钥)创建的SNK文件非常满意,但问题是我有一个PFX文件...
我可以使用sn.exe -p将公钥从PFX导出到SNK,但它只导出公钥,TlbImp.exe完全不喜欢它。我可以导出公钥和私钥吗?

我尝试使用
安装PFX文件 sn.exe -i MyCompany.pfx xyz
然后使用
导入类型库 TlbImp.exe / keycontainer:xyz ...“,
但这给了我 TlbImp:错误TI0000:指定的强名称参数无效。

我该怎么办? 谢谢!

更新:Oleg的回答和他的实用工具完美无缺。 PFX文件(重新导出后)可以成功用于提取 公钥( sn.exe -p )与tlbimp.exe一起使用( tlbimp.exe /publickey:xyz.pub )。然后可以使用PFX文件重新签名互操作 dll( sn.exe -R

Comodo确实在这个球上丢球。下面是转储( certutil.exe -dump -v xyz.pfx )原始和固定的PFX文件:

之前:

CERT_KEY_PROV_INFO_PROP_ID(2):
钥匙箱= {36BDD7BD-F295-47B2-B9E7-C25BD5B4313E}
唯一容器名称: bf63afd9ba3fb912ccd3423c6486e5fc_25e0623f-f712-49e2 - 阿卜达,f31f014b5dae
Provider = Microsoft Enhanced Cryptographic Provider v1.0
ProviderType = 1
标志= 0
KeySpec = 1 - AT_KEYEXCHANGE
...
私钥:
PRIVATEKEYBLOB
版本:2
aiKeyAlg:0xa400
CALG_RSA_KEYX
算法类: 0xa000(5)ALG_CLASS_KEY_EXCHANGE
算法类型:0x400(2)ALG_TYPE_RSA
算法子ID:0x0(0)ALG_SID_RSA_ANY

后:

CERT_KEY_PROV_INFO_PROP_ID(2):
钥匙箱= {DBA6454E-F6D2-4F0B-AB1B-9E4F7C0E139C}
唯一容器名称: d2d09f87081c1af7c4225889f1af2250_25e0623f-f712-49e2 - 阿卜达,f31f014b5dae
Provider = Microsoft Enhanced Cryptographic Provider v1.0
ProviderType = 1
标志= 0
KeySpec = 2 - AT_SIGNATURE
...
私钥:
PRIVATEKEYBLOB
版本:2
aiKeyAlg:0x2400
CALG_RSA_SIGN
算法类: 0x2000(1)ALG_CLASS_SIGNATURE
算法类型:0x400(2)ALG_TYPE_RSA
算法子ID:0x0(0)ALG_SID_RSA_ANY

1 个答案:

答案 0 :(得分:5)

尝试执行以下操作

sn.exe -p MyCompany.pfx MyCompany.pub
tlbimp.exe My.dll /delaysign /publickey:MyCompany.pub /out:Interop.My.dll
sn.exe -R Interop.My.dll MyCompany.pfx

如果您从COM DLL的tlb文件创建主互操作程序集,则表单中将tlbimp.exe

tlbimp.exe My.tlb /primary /delaysign /publickey:MyCompany.pub /out:Interop.My.dll

您还可以使用互操作程序集的代码签名:

signtool.exe sign /f MyCompany.pfx /p password /t http://timestamp.verisign.com/scripts/timstamp.dll /v Interop.My.dll

更新:我找到了你问题的原因。首先,我应该解释(或提醒)关键容器的一个特征。

私钥存在于密钥容器中。密钥容器可以位于文件夹%APPDATA%\Microsoft\Crypto\RSA\YourUserSid的某个文件中的本地硬盘上。以同样的方式,密钥容器可以是PFX文件的一部分。在两种情况下,一个密钥容器最多可以保存两个私钥:一个用于数字签名(AT_SIGNATURE),另一个用于密钥交换(AT_KEYEXCHANGE。您可以查看CryptGenKey函数的参数以获取更多详细信息。两个键都可以完全不同。例如,您可以保留一个大小为4096的密钥容器私钥的数字签名,以及大小为2048的同一容器另一个私钥的密钥交换部分。

证书CERT_KEY_PROV_INFO_PROP_IDdwKeySpec。它保存AT_SIGNATURE结构AT_KEYEXCHANGE(值AT_KEYEXCHANGE | AT_SIGNATUREpwszContainerName或其他类似le-fe9728d2-af26-4f15-9be0-48c5af6f21dc)字段与其他字段{{1}一起使用(例如pwszProvName),dwProvType(" Microsoft增强加密提供程序v1.0"例如),PROV_RSA_FULL(例如SignTool.exe) 。因此,人们完全可以参考证书中的密钥。

问题是您拥有的证书将密钥保存在容器的密钥交换部分而不是数字签名部分。使用tlbimp.exe工具时没有问题,但如果使用CN = UTN-USERFirst-Object OU = http://www.usertrust.com O = The USERTRUST Network L = Salt Lake City S = UT C = US 则会出现错误

  

TlbImp:错误TI1000:类型库导入器遇到错误   意外异常:System.Security.SecurityException - 无效   装配公钥。 (来自HRESULT的异常:0x8013141E)

我认为这是Comodo的常见问题,或者可能是通过Tucows得到的Comodo证书。

MakeCert.exe

要重现我所描述的问题,您可以执行以下步骤:

1)创建密钥对,将其保存在新密钥容器的密钥交换中,并创建密钥对的自签名证书。我们可以使用例如'myKeyContainer'的以下参数来执行此操作:

  

MakeCert.exe -pe -ss MY -a sha1 -cy authority -len 2048 -e 12/31/2020   -r -n" CN =我的公司根权限,O =我的公司,C = DE" -eku 1.3.6.1.5.5.7.3.3 -sky exchange -len 2048 -sk myKeyContainer

在上面的示例中,我们将新密钥保存在具有明确指定名称CertUtil.exe的密钥容器中,以便将来更容易查找和检查它。在实践中,不要指定参数,并且将根据新的GUID自动生成容器名称。

2)使用"证书"管理单元从MMC.EXE导出新创建的证书作为PFX文件。您必须在导出期间选择密码。或者,您可以使用sn.exe执行相同的操作。

  

CertUtil.exe -privatekey -user -exportpfx -p yourPassword"我的公司根   权威" MyCompany.pfx

3)以SNK格式导出密钥的公共部分。我更喜欢使用.PUB扩展名而不是.SNK来区分它与保存密钥对的.SNK文件更清楚。可以使用tlbimp.exe和-p参数来执行此操作:

  

sn.exe -p MyCompany.pfx MyCompany.pub

4)现在可以使用-sky exchange重现错误。

  

tlbimp.exe My.tlb / machine:X86 / primary / delaysign   /pkeykey:MyCompany.pub /out:Interop.My.dll

如果您在第一步不使用开关tlbimp.exeInterop.My.dll的使用将成功,您可以在下一步中使用

  

sn.exe -R Interop.My.dll MyCompany.pfx

签署AT_SIGNATURE。之后,您可以使用代码签名来另外删除

  

signtool.exe sign / f MyCompany.pfx / p yourPassword / t   certificate extended property / v Interop.My.dll

对于长文本感到抱歉,但我想确保那些遇到同样问题的人能够理解它。

要解决此问题,您需要使用任何可以导出和导入私钥的工具。您应该从容器的密钥交换部分导出密钥,并将其导入数字签名部分的同一容器中。如果你有问题找到相应的工具我可以尝试帮助。我不太了解现有的工具,但我知道如何编写程序来实现这一点。在.NET的情况下,我将使用CRYPT_KEY_PROV_INFO导出密钥并使用http://timestamp.verisign.com/scripts/timstamp.dll将其导回。应该只更改密钥blob中的一个位以使用KeyNumber.Exchange而不是AT_KEYEXCHANGE。如果您打开密钥容器,则应在CspParameters.KeyNumber参数后使用KeyNumber.Signature,在下一步中使用REG_DWORD

更新2 :我发现ExportCspBlob描述符合我的假设。建议的一种解决方法是在密钥KeySpec中设置AT_SIGNATURE值,其名称为HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\StrongName,值为2(tlbimp.exe)。我对此进行了测试,但对ChangeKeyProvInfo.exe的使用没有帮助。

所以我刚刚编写了完成所有工作的实用工具。您应该导入PFX文件,然后使用证书问题名称启动我的实用程序ChangeKeyProvInfo.exe "Dmitry Streblechenko" 。例如

#define STRICT

#include <windows.h>
#include <wincrypt.h>
#include <tchar.h>

#pragma comment (lib, "Crypt32.lib")

int _tmain(int argc, LPCTSTR *argv)
{
    HCERTSTORE hStore = NULL;
    PCCERT_CONTEXT pCertContext = NULL;
    LPCTSTR pszCertSubjectName = NULL;
    BOOL bSuccess, bResult = FALSE;
    DWORD cbData = 0, dwKeySpec = 0;
    HCRYPTKEY hKey = 0, hKeyNew = 0;
    BOOL fCallerFreeProvOrNCryptKey;
    PBYTE pPrivateKeyBlob = NULL;
    PUBLICKEYSTRUC *pPublicKey = NULL;
    HCRYPTPROV_OR_NCRYPT_KEY_HANDLE hCryptProvOrNCryptKey = 0;
    PCRYPT_KEY_PROV_INFO pKeyProvInfo = NULL;

    if (argc != 2) {
        _tprintf(TEXT("USAGE:\r\n\tChangeKeyProvInfo.exe CertSubjectName\n"));
        return 1;
    }
    pszCertSubjectName = argv[1];

    __try {
        hStore = CertOpenStore (CERT_STORE_PROV_SYSTEM, 
            X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
            (HCRYPTPROV_LEGACY)0,
            CERT_STORE_ENUM_ARCHIVED_FLAG | CERT_STORE_OPEN_EXISTING_FLAG | CERT_SYSTEM_STORE_CURRENT_USER,
            TEXT("MY"));
        if (hStore == NULL) {
            _tprintf(TEXT("Error in CertOpenStore(): %d"), GetLastError());
            __leave;
        }

        pCertContext = CertFindCertificateInStore (hStore,
            X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
            0,
            CERT_FIND_SUBJECT_STR,
            pszCertSubjectName,
            NULL);
        if (pCertContext == NULL) {
            _tprintf(TEXT("Error in CertFindCertificateInStore(): %d"), GetLastError());
            __leave;
        }

        bSuccess = CryptAcquireCertificatePrivateKey(pCertContext,
            CRYPT_ACQUIRE_COMPARE_KEY_FLAG | CRYPT_ACQUIRE_NO_HEALING,
            NULL,
            &hCryptProvOrNCryptKey,
            &dwKeySpec,
            &fCallerFreeProvOrNCryptKey);
        if (!bSuccess) {
            _tprintf(TEXT("Error in CryptAcquireCertificatePrivateKey(): %d"), GetLastError());
            __leave;
        }

        bSuccess = CryptGetUserKey (hCryptProvOrNCryptKey, dwKeySpec, &hKey);
        if (!bSuccess) {
            _tprintf(TEXT("Error in CryptGetUserKey(): %d"), GetLastError());
            __leave;
        }

        // get privale key as clear text data in form PRIVATEKEYBLOB
        cbData = 0;
        bSuccess = CryptExportKey (hKey, 0, PRIVATEKEYBLOB, 0, NULL, &cbData);
        if (!bSuccess) {
            _tprintf(TEXT("Error in CryptExportKey(): %d"), GetLastError());
            __leave;
        }

        pPrivateKeyBlob = (PBYTE)LocalAlloc(LPTR, cbData);
        if (pPrivateKeyBlob == NULL) {
            _tprintf(TEXT("Error in LocalAlloc(): %d"), GetLastError());
            __leave;
        }

        bSuccess = CryptExportKey (hKey, 0, PRIVATEKEYBLOB, 0, pPrivateKeyBlob, &cbData);
        if (!bSuccess) {
            _tprintf(TEXT("Error in CryptExportKey(): %d"), GetLastError());
            __leave;
        }

        // the PRIVATEKEYBLOB started with the PUBLICKEYSTRUC which contains the KeyAlg
        // CALG_RSA_KEYX are used for AT_KEYEXCHANGE
        // CALG_RSA_SIGN are used for AT_SIGNATURE
        pPublicKey = (PUBLICKEYSTRUC *)pPrivateKeyBlob;
        if (pPublicKey->aiKeyAlg == CALG_RSA_SIGN) {
            _tprintf(TEXT("Currently AT_SIGNATURE are used - nothing to do.\n"));
            __leave;
        } else if (pPublicKey->aiKeyAlg != CALG_RSA_KEYX) {
            _tprintf(TEXT("ERROR: Unknown algorithm 0x%X are used\n"), pPublicKey->aiKeyAlg);
            __leave;
        }

        // !!!!!! the next line in the most important !!!!!!
        pPublicKey->aiKeyAlg = CALG_RSA_SIGN; 
        bSuccess = CryptImportKey (hCryptProvOrNCryptKey, pPrivateKeyBlob, cbData, (HCRYPTKEY)NULL, CRYPT_EXPORTABLE, &hKeyNew);
        if (!bSuccess) {
            _tprintf(TEXT("Error in CertGetCertificateContextProperty(): %d"), GetLastError());
            __leave;
        }
        _tprintf(TEXT("The key container has successfully imported the same key for the AT_SIGNATURE usage.\n"));

        bSuccess = CertGetCertificateContextProperty (pCertContext, CERT_KEY_PROV_INFO_PROP_ID, 0, &cbData);
        if (!bSuccess) {
            _tprintf(TEXT("Error in CertGetCertificateContextProperty(): %d"), GetLastError());
            __leave;
        }
        pKeyProvInfo = (PCRYPT_KEY_PROV_INFO)LocalAlloc (LPTR, cbData);
        if (pKeyProvInfo == NULL) {
            _tprintf(TEXT("Error in LocalAlloc(): %d"), GetLastError());
            __leave;
        }

        bSuccess = CertGetCertificateContextProperty (pCertContext, CERT_KEY_PROV_INFO_PROP_ID, pKeyProvInfo, &cbData);
        if (!bSuccess) {
            _tprintf(TEXT("Error in CertGetCertificateContextProperty(): %d"), GetLastError());
            __leave;
        }
        // !!!!!! the next line in the most important !!!!!!
        pKeyProvInfo->dwKeySpec = AT_SIGNATURE;
        bSuccess = CertSetCertificateContextProperty (pCertContext, CERT_KEY_PROV_INFO_PROP_ID, CERT_SET_PROPERTY_IGNORE_PERSIST_ERROR_FLAG, pKeyProvInfo);
        if (!bSuccess) {
            _tprintf(TEXT("Error in CertSetCertificateContextProperty(): %d"), GetLastError());
            __leave;
        }
        _tprintf(TEXT("The certificale property was successfully changed to use the key with AT_SIGNATURE.\n"));
        bResult = TRUE;
    }
    __finally {
        if (pPrivateKeyBlob)
            pPrivateKeyBlob = (PBYTE) LocalFree (pPrivateKeyBlob);

        if (pKeyProvInfo)
            pKeyProvInfo = (PCRYPT_KEY_PROV_INFO) LocalFree (pKeyProvInfo);

        if (pCertContext)
            bSuccess = CertFreeCertificateContext (pCertContext);

        if (hKeyNew)
            bSuccess = CryptDestroyKey (hKeyNew);

        if (hCryptProvOrNCryptKey)
            bSuccess = CryptReleaseContext (hCryptProvOrNCryptKey, 0);

        if (hStore)
            bSuccess = CertCloseStore (hStore, CERT_CLOSE_STORE_CHECK_FLAG);
    }

    return bResult? 0: 1;
}

之后,我使用与密钥交换部分相同的私钥在密钥容器中添加数字签名部分。然后我更改证书的属性以使用密钥容器的数字签名部分。

您可以下载实用程序ImportCspBlobhere源代码。

为了更容易找到我在下面发布的代码。您应该将代码视为我今天编写的快速而肮脏的解决方案,因此代码并不完美。

certutil.exe -dump -v My.pfx

更新2 here您可以下载一个实验的完整协议,每个人都可以重复以重现问题我是如何理解的。可以使用... CERT_KEY_PROV_INFO_PROP_ID(2): ... KeySpec = 1 -- AT_KEYEXCHANGE ... Private Key: PRIVATEKEYBLOB Version: 2 aiKeyAlg: 0xa400 CALG_RSA_KEYX Algorithm Class: 0xa000(5) ALG_CLASS_KEY_EXCHANGE ... Encryption test passed 分析here中保存的第一个证书。您可以看到Here的完整输出。输出中最重要的部分是:

certutil.exe -dump -v My.pfx

修改后的证书将保存在My.pfx中。 ... CERT_KEY_PROV_INFO_PROP_ID(2): ... KeySpec = 2 -- AT_SIGNATURE ... Private Key: PRIVATEKEYBLOB Version: 2 aiKeyAlg: 0x2400 CALG_RSA_SIGN Algorithm Class: 0x2000(1) ALG_CLASS_SIGNATURE ... Signature test passed 的{​​{3}}表示问题已解决:

{{1}}

更新3: 3年后,Comodo签名的问题仍然存在。以下是他们的回复。下次我的证书需要续签时,我会和其他人一起去。


我们的代码签名证书旨在用于Microsoft Authenticode签名(以及其他与对象签名相关的项目;例如jar签名),并不适用于Microsoft Strong Name签名程序集。如果Microsoft的应用程序没有使用密钥对,并且keySpec设置为其建议的默认值(1,AT_KEYEXCHANGE;它允许签名和加密)并且仅适用于AT_SIGNATURE(仅允许签名),那么它将是要成为Microsoft的实用程序的处理问题,您需要与Microsoft打开一个案例来解决问题。我们有成千上万的客户能够应用Microsoft Authenticode&amp;其他与对象相关的签名没有问题,此keySpec设置为原样。我们不愿意为几乎每个人修改一个完美无缺的流程,除了少数希望走极端并使用公开信任的CA对strongName签署其程序集的人。我们遇到的大多数开发人员不使用CA签名证书进行强名称签名程序集,而是使用自签名证书,因为公共CA对所有证书施加的最大期限限制(最多不超过3 - 5年)。 )

根据stackoverflow帖子,您创建了一个实用程序来纠正&amp;确实为您提供了针对您的具体问题的解决方法,因此遗憾的是,我们无法就此事提出任何建议。

对于给您带来的不便,我们深表歉意。

亲切的问候,

技术支持