From 5e81ab8e470acafcf18b2c787b9087596202f9cd Mon Sep 17 00:00:00 2001 From: va4es2 Date: Wed, 24 Apr 2024 14:43:38 +0300 Subject: [PATCH] =?UTF-8?q?bug(cryptopro-cades):=20=D0=9A=D0=BE=D1=80?= =?UTF-8?q?=D1=80=D0=B5=D0=BA=D1=82=D0=BD=D0=BE=D0=B5=20=D0=BE=D0=BF=D1=80?= =?UTF-8?q?=D0=B5=D0=B4=D0=B5=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BD=D0=B0?= =?UTF-8?q?=D0=BB=D0=B8=D1=87=D0=B8=D1=8F=20=D0=BA=D0=BE=D0=BD=D1=82=D0=B5?= =?UTF-8?q?=D0=B9=D0=BD=D0=B5=D1=80=D0=B0=20=D0=B7=D0=B0=D0=BA=D1=80=D1=8B?= =?UTF-8?q?=D1=82=D0=BE=D0=B3=D0=BE=20=D0=BA=D0=BB=D1=8E=D1=87=D0=B0=20?= =?UTF-8?q?=D1=81=D0=B5=D1=80=D1=82=D0=B8=D1=84=D0=B8=D0=BA=D0=B0=D1=82?= =?UTF-8?q?=D0=B0=20(#47)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bug(cryptopro-cades): Поправил корректное определение наличия контейнера закрытого ключа в системе. feat(cryptopro-cades): Добавил необязательные параметры функциям получения списка сертификатов, подписи, подписи хэша файла для гибкости работы с ними. Co-authored-by: va4es2 --- packages/cryptopro-cades/src/Certificate.ts | 33 +++++++++++-------- .../src/api/getCertificates.ts | 27 ++++++++------- packages/cryptopro-cades/src/api/sign.ts | 4 ++- packages/cryptopro-cades/src/api/signHash.ts | 4 ++- .../src/types/cadesplugin/ICertificate.ts | 6 ++++ 5 files changed, 48 insertions(+), 26 deletions(-) diff --git a/packages/cryptopro-cades/src/Certificate.ts b/packages/cryptopro-cades/src/Certificate.ts index 1fc5094..92ad139 100644 --- a/packages/cryptopro-cades/src/Certificate.ts +++ b/packages/cryptopro-cades/src/Certificate.ts @@ -145,10 +145,11 @@ export class Certificate { /** * Распарсить сертификат из исходного объекта. * @param {ICertificate} cert исходный сертификат. + * @param {boolean} [checkPrivateKey=true] проводить проверку наличия закрытого ключа. * @throws {CryptoError} в случае ошибки. * @returns {Promise} распрасенный сертификат. */ - public static async CreateFrom(cert: ICertificate): Promise { + public static async CreateFrom(cert: ICertificate, checkPrivateKey: boolean = true): Promise { if (!cert) { const errorMessage = 'Не указаны данные исходного сертификата.'; @@ -170,21 +171,27 @@ export class Certificate { cert.Export(CAPICOM_ENCODING_TYPE.CAPICOM_ENCODE_BASE64), ); - try { - certificate.hasPrivateKey = await unwrap(cert.HasPrivateKey()); + if (checkPrivateKey) { + try { + certificate.hasPrivateKey = await unwrap(cert.HasPrivateKey()); - const oPrivateKey = await unwrap(cert.PrivateKey); + const oPrivateKey = await unwrap(cert.PrivateKey); - certificate.providerName = await unwrap(oPrivateKey.ProviderName); - certificate.providerType = await unwrap(oPrivateKey.ProviderType); - } catch (error) { - // ошибка не критична, просто создаем ошибку (в дебаге оно залогируется само) - CryptoError.createCadesError( - error, - `Ошибка получения информации о приватном ключе сертификата ${certificate.thumbprint}.`, - ); + certificate.providerName = await unwrap(oPrivateKey.ProviderName); + certificate.providerType = await unwrap(oPrivateKey.ProviderType); - certificate.hasPrivateKey = false; + if (certificate.hasPrivateKey) { + await unwrap(cert.FindPrivateKey()); + } + } catch (error) { + // ошибка не критична, просто создаем ошибку (в дебаге оно залогируется само) + CryptoError.createCadesError( + error, + `Ошибка получения информации о приватном ключе сертификата ${certificate.thumbprint}.`, + ); + + certificate.hasPrivateKey = false; + } } parseCertificate(certificate); diff --git a/packages/cryptopro-cades/src/api/getCertificates.ts b/packages/cryptopro-cades/src/api/getCertificates.ts index 7c5aac8..22b9ee0 100644 --- a/packages/cryptopro-cades/src/api/getCertificates.ts +++ b/packages/cryptopro-cades/src/api/getCertificates.ts @@ -21,10 +21,11 @@ const certificatesCache = {}; /** * Возвращает список сертификатов из указанного хранилища. * @param {IStore} store Хранилище + * @param {boolean} [checkPrivateKey=true] проводить проверку наличия закрытого ключа. * @throws {CryptoError} в случае ошибки. * @returns {Promise} .Список сертификатов. */ -async function getCertificatesFromStore(store: IStore): Promise { +async function getCertificatesFromStore(store: IStore, checkPrivateKey: boolean = true): Promise { if (!store) { const errorMessage = 'Не задано хранилище сертификатов.'; @@ -51,7 +52,7 @@ async function getCertificatesFromStore(store: IStore): Promise { const certBin: ICertificate = await unwrap( certificates.Item(certificatesCount--), ); - const cert: Certificate = await Certificate.CreateFrom(certBin); + const cert: Certificate = await Certificate.CreateFrom(certBin, checkPrivateKey); // работаем только с гостовскими сертами if (cert.isGost) { @@ -68,16 +69,17 @@ async function getCertificatesFromStore(store: IStore): Promise { /** * Получить сертификаты из USB токенов. + * @param {boolean} [checkPrivateKey=true] проводить проверку наличия закрытого ключа. * @throws {CryptoError} в случае ошибки. * @returns {Promise} .Список сертификатов из USB токенов. */ -async function ReadCertificatesFromUsbToken(): Promise { +async function ReadCertificatesFromUsbToken(checkPrivateKey: boolean = true): Promise { let store: IStore | null = null; try { store = await openStore(STORE_LOCATION.CADESCOM_CONTAINER_STORE); - return await getCertificatesFromStore(store); + return await getCertificatesFromStore(store, checkPrivateKey); } finally { await store?.Close(); } @@ -85,10 +87,11 @@ async function ReadCertificatesFromUsbToken(): Promise { /** * Получить сертификаты из реестра. + * @param {boolean} [checkPrivateKey=true] проводить проверку наличия закрытого ключа. * @throws {CryptoError} в случае ошибки. * @returns {Promise} .Список сертификатов из реестра. */ -async function ReadCertificatesFromRegistry(): Promise { +async function ReadCertificatesFromRegistry(checkPrivateKey: boolean = true): Promise { let store: IStore | null = null; try { @@ -98,7 +101,7 @@ async function ReadCertificatesFromRegistry(): Promise { CAPICOM_STORE_OPEN_MODE.CAPICOM_STORE_OPEN_MAXIMUM_ALLOWED, ); - return await getCertificatesFromStore(store); + return await getCertificatesFromStore(store, checkPrivateKey); } finally { await store?.Close(); } @@ -109,12 +112,14 @@ async function ReadCertificatesFromRegistry(): Promise { * * @param {STORE_TYPE} storeType из какого хранилища требуется получить сертификаты (из токена, реестра, все...). * @param {resetCache} resetCache перезапросить данные, игнорируя закэшированные данные. + * @param {boolean} [checkPrivateKey=true] проводить проверку наличия закрытого ключа. * @throws {CryptoError} в случае ошибки. * @returns {Promise} .сертификаты. */ export function getCertificates( storeType: STORE_TYPE = STORE_TYPE.ALL, resetCache: boolean = false, + checkPrivateKey: boolean = true, ): Promise { if (certificatesCache[storeType] && !resetCache) { return Promise.resolve(certificatesCache[storeType]); @@ -131,23 +136,23 @@ export function getCertificates( try { switch (storeType) { case STORE_TYPE.USB_TOKEN: - result = await ReadCertificatesFromUsbToken(); + result = await ReadCertificatesFromUsbToken(checkPrivateKey); logData.push({ storeType, result }); break; case STORE_TYPE.REGISTRY: - result = await ReadCertificatesFromRegistry(); + result = await ReadCertificatesFromRegistry(checkPrivateKey); logData.push({ storeType, result }); break; case STORE_TYPE.ALL: - const usbTokenCertificates = await ReadCertificatesFromUsbToken(); + const usbTokenCertificates = await ReadCertificatesFromUsbToken(checkPrivateKey); logData.push({ storeType: 'usb', usbTokenCertificates }); - const certificatesFromRegistry = await ReadCertificatesFromRegistry(); + const certificatesFromRegistry = await ReadCertificatesFromRegistry(checkPrivateKey); logData.push({ storeType: 'registry', certificatesFromRegistry }); result = usbTokenCertificates.concat(certificatesFromRegistry); @@ -165,7 +170,7 @@ export function getCertificates( try { store = await openStore(); - result = await getCertificatesFromStore(store); + result = await getCertificatesFromStore(store, checkPrivateKey); logData.push({ storeType: 'default', result }); } finally { await store?.Close(); diff --git a/packages/cryptopro-cades/src/api/sign.ts b/packages/cryptopro-cades/src/api/sign.ts index 8f86151..c807501 100644 --- a/packages/cryptopro-cades/src/api/sign.ts +++ b/packages/cryptopro-cades/src/api/sign.ts @@ -24,6 +24,7 @@ import { unwrap } from './internal/unwrap'; * @param {boolean} [detach=true] присоединять подпись к данным или отдельно? * @param {boolean} [includeCertChain=true] - включать в результат всю цепочку сертификатов. * @param {boolean} [doNotValidate=false] - не проводить валидацию сертификатов. + * @param {CADESCOM_CADES_TYPE} [cadesType=CADESCOM_CADES_TYPE.CADESCOM_CADES_BES] - тип усовершенствованной подписи (см. CADESCOM_CADES_TYPE). * @throws {CryptoError} в случае ошибки. * @returns файл подписи в кодировке Base64. */ @@ -33,6 +34,7 @@ export function sign( detach: boolean = true, includeCertChain: boolean = true, doNotValidate: boolean = false, + cadesType: CADESCOM_CADES_TYPE = CADESCOM_CADES_TYPE.CADESCOM_CADES_BES, ): Promise { return afterPluginLoaded(async () => { const logData = []; @@ -124,7 +126,7 @@ export function sign( const signResult = await unwrap( signedData.SignCades( signer, - CADESCOM_CADES_TYPE.CADESCOM_CADES_BES, + cadesType, detach, ), ); diff --git a/packages/cryptopro-cades/src/api/signHash.ts b/packages/cryptopro-cades/src/api/signHash.ts index d849706..4196ce1 100644 --- a/packages/cryptopro-cades/src/api/signHash.ts +++ b/packages/cryptopro-cades/src/api/signHash.ts @@ -51,6 +51,7 @@ function selectAlgoritm(cert: Certificate): CADESCOM_HASH_ALGORITHM { * 4A5F6E54CA44064A5544943DDC244DDC84DC3952AC5924A475838E7BB8320878 * @param {boolean} [includeCertChain=true] - включать в результат всю цепочку сертификатов. * @param {boolean} [doNotValidate=false] - не проводить валидацию сертификатов. + * @param {CADESCOM_CADES_TYPE} [cadesType=CADESCOM_CADES_TYPE.CADESCOM_CADES_BES] - тип усовершенствованной подписи (см. CADESCOM_CADES_TYPE). * @throws {CryptoError} в случае ошибки. * @returns файл подписи в кодировке Base64. */ @@ -59,6 +60,7 @@ export function signHash( data: ArrayBuffer | string, includeCertChain: boolean = true, doNotValidate: boolean = false, + cadesType: CADESCOM_CADES_TYPE = CADESCOM_CADES_TYPE.CADESCOM_CADES_BES, ): Promise { return afterPluginLoaded(async () => { const logData = []; @@ -147,7 +149,7 @@ export function signHash( signedData.SignHash( hashedData, signer, - CADESCOM_CADES_TYPE.CADESCOM_CADES_BES, + cadesType, ), ); diff --git a/packages/cryptopro-cades/src/types/cadesplugin/ICertificate.ts b/packages/cryptopro-cades/src/types/cadesplugin/ICertificate.ts index ed9333b..5c6e67a 100644 --- a/packages/cryptopro-cades/src/types/cadesplugin/ICertificate.ts +++ b/packages/cryptopro-cades/src/types/cadesplugin/ICertificate.ts @@ -8,6 +8,7 @@ import type { WithOptionalPromise } from '../WithOptionalPromise'; * Описывает сертификат открытого ключа. * @see https://docs.microsoft.com/en-us/windows/win32/seccrypto/certificate * @see https://docs.cryptopro.ru/cades/reference/cadescom/cadescom_class/cpcertificate + * @see https://docs.cryptopro.ru/cades/reference/cadescom/cadescom_interface/icpcertificate */ export interface ICertificate { /** @@ -82,4 +83,9 @@ export interface ICertificate { */ // eslint-disable-next-line @typescript-eslint/no-explicit-any IsValid(): WithOptionalPromise; + + /** + * Производит поиск закрытого ключа соответствующего сертификату и устанавливает ссылку на него. + */ + FindPrivateKey(): WithOptionalPromise; }