Создание строго названной сборки COM-взаимодействия с файлом PFX - PullRequest
4 голосов
/ 03 декабря 2011

Я пытаюсь создать сборку COM-взаимодействия со строгим именем с файлом PFX.
TlbImp.exe очень доволен файлом SNK, созданным с помощью sn.exe -k (который содержит как открытый, так и закрытый ключи), но проблема в том, что у меня есть файл PFX ...
Я могу экспортировать открытый ключ из PFX в SNK, используя sn.exe -p, но он экспортирует только открытый ключ, который TlbImp.exe совсем не нравится. Можно ли экспортировать как открытые, так и закрытые ключи?

Я пытался установить файл PFX, используя
sn.exe -i MyCompany.pfx xyz
а затем импортируйте библиотеку типов, используя
TlbImp.exe / keycontainer: xyz ... ",
но это дает мне
TlbImp: ошибка TI0000: указаны неверные параметры строгого имени.

Что я могу сделать?
Спасибо!

ОБНОВЛЕНИЕ : Ответ Олега и его полезность ниже работают отлично. Файл 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-abda-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
Подидентификатор алгоритма: 0x0 (0) ALG_SID_RSA_ANY

После того, как:

CERT_KEY_PROV_INFO_PROP_ID (2):
Контейнер ключей = {DBA6454E-F6D2-4F0B-AB1B-9E4F7C0E139C}
Уникальное имя контейнера: d2d09f87081c1af7c4225889f1af2250_25e0623f-f712-49e2-abda-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
Подидентификатор алгоритма: 0x0 (0) ALG_SID_RSA_ANY

1 Ответ

5 голосов
/ 03 декабря 2011

Попробуйте сделать следующее

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

Если вы создадите основную сборку взаимодействия из tlb-файла COM DLL, вы 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

ОБНОВЛЕНО : Я нашел причину вашей проблемы, которая у вас есть.Прежде всего я должен объяснить (или напомнить) об одной особенности контейнера ключей.

Закрытый ключ существует в контейнере key .Контейнер ключа может находиться на локальном жестком диске в каком-то файле из папки %APPDATA%\Microsoft\Crypto\RSA\YourUserSid.Точно так же контейнер ключей может быть частью файла PFX.В обоих случаях один контейнер ключей может сохранить до двух закрытых ключей: один для цифровой подписи (AT_SIGNATURE) и другой для обмена ключами (AT_KEYEXCHANGE) .Вы можете просмотреть параметры функции CryptGenKey для более подробной информации.Оба ключа могут быть абсолютно разными.Например, вы можете хранить в цифровой подписи один закрытый ключ контейнера с ключом размера 4096 и в части обмена ключами того же контейнера другой закрытый ключ размера 2048.

Сертификат имеет CERT_KEY_PROV_INFO_PROP_ID как единицу из его расширенного свойства сертификата .Он сохраняет структуру CRYPT_KEY_PROV_INFO , которая dwKeySpec (со значением AT_SIGNATURE, AT_KEYEXCHANGE или некоторым другим, например, AT_KEYEXCHANGE | AT_SIGNATURE) используется вместе с другими полями pwszContainerName (le-fe9728d2-af26-4f15-9be0-48c5af6f21dc, например, например)), pwszProvName (например, «Microsoft Enhanced Cryptographic Provider v1.0»), dwProvType (PROV_RSA_FULL, например).Таким образом, каждый имеет полную ссылку на ключ из сертификата.

Проблема в том, что у вашего сертификата есть ключ, сохраненный в части обмена ключами контейнера, а не в части цифровой подписи .Это не создает проблем в случае использования инструмента SignTool.exe, но в случае использования tlbimp.exe вы получаете ошибку

TlbImp: ошибка TI1000: Импортер библиотеки типов обнаружил непредвиденное исключение:System.Security.SecurityException - Неверный открытый ключ сборки.(Исключение из HRESULT: 0x8013141E)

Я полагаю, что это общая проблема Comodo или, возможно, Comodo сертификатов, которые получают через Tucows.

CN = UTN-USERFirst-Object
OU = http://www.usertrust.com
O = The USERTRUST Network
L = Salt Lake City
S = UT
C = US

Чтобы воспроизвести проблему, которую я описал, вы можете сделать следующие шаги:

1) Создать пару ключей, сохранить ее в обмене ключами нового контейнера ключей и создать собственнуюподписанный сертификат, который вы пару ключей.Для этого мы можем использовать, например, следующий параметр MakeCert.exe:

MakeCert.exe -pe -ss MY -a sha1 -cy полномочия -len 2048 -e 31.12.2020 -r -n "CN = корневая администрация моей компании, O = моя компания, C = DE" -eku 1.3.6.1.5.5.7.3.3 -sky exchange -len 2048 -sk myKeyContainer

InВ приведенном выше примере мы сохраняем новый ключ в контейнере ключей с явным указанным именем 'myKeyContainer', чтобы упростить его поиск и проверку в будущем.На практике параметр не указывается, и имя контейнера будет генерироваться автоматически на основе нового GUID.

2) Используйте оснастку «Сертификаты» для экспорта из MMC.EXE нового созданного сертификата в виде файла PFX.,Вы должны выбрать пароль во время экспорта.В качестве альтернативы вы можете использовать CertUtil.exe, чтобы сделать то же самое.

CertUtil.exe -privatekey -user -exportpfx -p yourPassword "Root Authority моей компании" MyCompany.pfx

3) Экспорт публичной части ключа в формате SNK.Я предпочитаю использовать расширение .PUB вместо .SNK, чтобы более четко отличить его от файла .SNK, содержащего пару ключей.Для этого можно использовать sn.exe с параметром -p:

sn.exe -p MyCompany.pfx MyCompany.pub

4) Теперь можно использовать tlbimp.exe для воспроизведения ошибки.

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

Если вы не используете переключатель -sky exchange на первом шаге, использование tlbimp.exe будет успешно, и вы можете использовать на следующем шаге

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

для подписи Interop.My.dll. После этого вы можете использовать подпись кода для dll дополнительно

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

Извините за длинный текст, но я хочу быть уверен, что люди, которые будут иметь такую ​​же проблему, смогут понять это.

Для решения этой проблемы вам понадобится любой инструмент, который может экспортировать и импортировать закрытый ключ. Вы должны экспортировать ключ из части обмена ключами контейнера и импортировать его в тот же контейнер в части цифровой подписи. Если у вас возникнут проблемы с поиском соответствующего инструмента, я могу попробовать помочь. Я не очень хорошо знаю существующие инструменты, но я знаю много способов, как написать программу, которая делает это. В случае .NET я бы использовал ExportCspBlob для экспорта ключа и используйте ImportCspBlob для его импорта обратно. Нужно изменить только один бит в блоке ключей, чтобы использовать AT_SIGNATURE вместо AT_KEYEXCHANGE. Если вы открываете контейнер ключей, вы должны использовать KeyNumber.Exchange один раз в качестве параметра CspParameters.KeyNumber и KeyNumber.Signature на следующем шаге.

ОБНОВЛЕНО 2 : Я нашел здесь описание, которое соответствует моим предположениям. Один из обходных путей, который предлагается, - это установить значение REG_DWORD с именем KeySpec и значением 2 (AT_SIGNATURE) в ключе HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\StrongName. Я проверял это, но это не помогает для использования tlbimp.exe.

Так что я просто написал утилиту, которая делает всю работу. Вы должны импортировать свой файл PFX и затем запустить мою утилиту ChangeKeyProvInfo.exe с именем выпуска сертификата. Например

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

ОБНОВЛЕНО 2 : Здесь вы можете загрузить полный протокол эксперимента, который каждый может повторить, чтобы воспроизвести проблему, как я понимаю, если. Первый сертификат, сохраненный в My.pfx , может быть проанализирован в отношении certutil.exe -dump -v My.pfx. Полный вывод вы можете увидеть здесь . Самая важная часть вывода:

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

Измененные сертификаты будут сохранены в My1.pfx . Вывод из certutil.exe -dump -v 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: Через 3 года проблема с подписями Comodo все еще присутствует. Ниже их ответ. В следующий раз, когда мой сертификат будет продлен, я пойду с кем-нибудь еще.


Наши сертификаты подписи кода предназначены для использования для подписей Microsoft Authenticode (и других связанных с подписью объектов элементов, например подписи jar) и не совсем предназначены для подписи Microsoft Strong Name для сборок.Если приложение Microsoft не будет использовать пару ключей с установленным по умолчанию значением keySpec (1, AT_KEYEXCHANGE; которое позволяет подписывать и шифровать) и работает только с AT_SIGNATURE (разрешает только подпись), то это будет проблема обработки сУтилита Microsoft, и вам нужно будет открыть дело с Microsoft, чтобы исправить проблему.У нас есть сотни тысяч клиентов, которые могут применять Microsoft Authenticode и другие связанные с объектами подписи без проблем с этим параметром keySpec, установленным как есть.Мы не хотим модифицировать процесс, который работает безупречно практически для всех, кроме нескольких человек, которые хотят пойти на крайние меры и использовать общедоступный CA для подписи strongName своих сборок.Большинство разработчиков, с которыми мы сталкиваемся, не используют подписанный ЦС сертификат для сборок с подписью Строго Имени, а используют самозаверяющий сертификат из-за максимальных сроков, которые публичные ЦС накладывают на все сертификаты (не более 3-5 лет максимум.)

Основываясь на сообщении stackoverflow, вы создали утилиту, которая исправляет и предоставляет вам обходной путь для вашей конкретной проблемы, поэтому, к сожалению, мы ничего не можем посоветовать в этом вопросе.

Приносим свои извинения за доставленные неудобства.

С уважением,

Техническая поддержка

...