Skip to content

Commit

Permalink
bug(cryptopro-cades): Корректное определение наличия контейнера закры…
Browse files Browse the repository at this point in the history
…того ключа сертификата (#47)

bug(cryptopro-cades): Поправил корректное определение наличия контейнера закрытого ключа в системе.
feat(cryptopro-cades): Добавил необязательные параметры функциям получения списка сертификатов, подписи, подписи хэша файла для гибкости работы с ними.

Co-authored-by: va4es2 <[email protected]>
  • Loading branch information
va4es2 and va4es2 authored Apr 24, 2024
1 parent 4b38682 commit 5e81ab8
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 26 deletions.
33 changes: 20 additions & 13 deletions packages/cryptopro-cades/src/Certificate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,11 @@ export class Certificate {
/**
* Распарсить сертификат из исходного объекта.
* @param {ICertificate} cert исходный сертификат.
* @param {boolean} [checkPrivateKey=true] проводить проверку наличия закрытого ключа.
* @throws {CryptoError} в случае ошибки.
* @returns {Promise<Certificate>} распрасенный сертификат.
*/
public static async CreateFrom(cert: ICertificate): Promise<Certificate> {
public static async CreateFrom(cert: ICertificate, checkPrivateKey: boolean = true): Promise<Certificate> {
if (!cert) {
const errorMessage = 'Не указаны данные исходного сертификата.';

Expand All @@ -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);
Expand Down
27 changes: 16 additions & 11 deletions packages/cryptopro-cades/src/api/getCertificates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ const certificatesCache = {};
/**
* Возвращает список сертификатов из указанного хранилища.
* @param {IStore} store Хранилище
* @param {boolean} [checkPrivateKey=true] проводить проверку наличия закрытого ключа.
* @throws {CryptoError} в случае ошибки.
* @returns {Promise<Certificate[]>} .Список сертификатов.
*/
async function getCertificatesFromStore(store: IStore): Promise<Certificate[]> {
async function getCertificatesFromStore(store: IStore, checkPrivateKey: boolean = true): Promise<Certificate[]> {
if (!store) {
const errorMessage = 'Не задано хранилище сертификатов.';

Expand All @@ -51,7 +52,7 @@ async function getCertificatesFromStore(store: IStore): Promise<Certificate[]> {
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) {
Expand All @@ -68,27 +69,29 @@ async function getCertificatesFromStore(store: IStore): Promise<Certificate[]> {

/**
* Получить сертификаты из USB токенов.
* @param {boolean} [checkPrivateKey=true] проводить проверку наличия закрытого ключа.
* @throws {CryptoError} в случае ошибки.
* @returns {Promise<Certificate[]>} .Список сертификатов из USB токенов.
*/
async function ReadCertificatesFromUsbToken(): Promise<Certificate[]> {
async function ReadCertificatesFromUsbToken(checkPrivateKey: boolean = true): Promise<Certificate[]> {
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();
}
}

/**
* Получить сертификаты из реестра.
* @param {boolean} [checkPrivateKey=true] проводить проверку наличия закрытого ключа.
* @throws {CryptoError} в случае ошибки.
* @returns {Promise<Certificate[]>} .Список сертификатов из реестра.
*/
async function ReadCertificatesFromRegistry(): Promise<Certificate[]> {
async function ReadCertificatesFromRegistry(checkPrivateKey: boolean = true): Promise<Certificate[]> {
let store: IStore | null = null;

try {
Expand All @@ -98,7 +101,7 @@ async function ReadCertificatesFromRegistry(): Promise<Certificate[]> {
CAPICOM_STORE_OPEN_MODE.CAPICOM_STORE_OPEN_MAXIMUM_ALLOWED,
);

return await getCertificatesFromStore(store);
return await getCertificatesFromStore(store, checkPrivateKey);
} finally {
await store?.Close();
}
Expand All @@ -109,12 +112,14 @@ async function ReadCertificatesFromRegistry(): Promise<Certificate[]> {
*
* @param {STORE_TYPE} storeType из какого хранилища требуется получить сертификаты (из токена, реестра, все...).
* @param {resetCache} resetCache перезапросить данные, игнорируя закэшированные данные.
* @param {boolean} [checkPrivateKey=true] проводить проверку наличия закрытого ключа.
* @throws {CryptoError} в случае ошибки.
* @returns {Promise<Certificate[]>} .сертификаты.
*/
export function getCertificates(
storeType: STORE_TYPE = STORE_TYPE.ALL,
resetCache: boolean = false,
checkPrivateKey: boolean = true,
): Promise<Certificate[]> {
if (certificatesCache[storeType] && !resetCache) {
return Promise.resolve(certificatesCache[storeType]);
Expand All @@ -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);
Expand All @@ -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();
Expand Down
4 changes: 3 additions & 1 deletion packages/cryptopro-cades/src/api/sign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -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<string> {
return afterPluginLoaded(async () => {
const logData = [];
Expand Down Expand Up @@ -124,7 +126,7 @@ export function sign(
const signResult = await unwrap(
signedData.SignCades(
signer,
CADESCOM_CADES_TYPE.CADESCOM_CADES_BES,
cadesType,
detach,
),
);
Expand Down
4 changes: 3 additions & 1 deletion packages/cryptopro-cades/src/api/signHash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -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<string> {
return afterPluginLoaded(async () => {
const logData = [];
Expand Down Expand Up @@ -147,7 +149,7 @@ export function signHash(
signedData.SignHash(
hashedData,
signer,
CADESCOM_CADES_TYPE.CADESCOM_CADES_BES,
cadesType,
),
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
/**
Expand Down Expand Up @@ -82,4 +83,9 @@ export interface ICertificate {
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
IsValid(): WithOptionalPromise<any>;

/**
* Производит поиск закрытого ключа соответствующего сертификату и устанавливает ссылку на него.
*/
FindPrivateKey(): WithOptionalPromise<void>;
}

0 comments on commit 5e81ab8

Please sign in to comment.