UNPKG

ruscryptojs

Version:

Simplified library for Russian GOST crypto providers.

1,326 lines (1,259 loc) 47.1 kB
/** * CryptoPRO simplified library * @author Aleksandr.ru * @link http://aleksandr.ru */ import DN from '../DN'; import { X509KeySpec, X509PrivateKeyExportFlags, X509CertificateEnrollmentContext, X509KeyUsageFlags, X500NameFlags, EncodingType, InstallResponseRestrictionFlags, ProviderTypes, cadesErrorMesages } from './constants'; import { convertDN, versionCompare } from '../helpers'; function CryptoPro() { //If the string contains fewer than 128 bytes, the Length field of the TLV triplet requires only one byte to specify the content length. //If the string is more than 127 bytes, bit 7 of the Length field is set to 1 and bits 6 through 0 specify the number of additional bytes used to identify the content length. const maxLengthCSPName = 127; // https://www.cryptopro.ru/forum2/default.aspx?g=posts&m=38467#post38467 const asn1UTF8StringTag = 0x0c; // 12, UTF8String let canAsync; let pluginVersion = ''; let binded = false; let signerOptions = 0; /** * Инициализация и проверка наличия требуемых возможностей * @returns {Promise<Object>} версия */ this.init = function(){ window.cadesplugin_skip_extension_install = true; // считаем что уже все установлено window.allow_firefox_cadesplugin_async = true; // FF 52+ require('./cadesplugin_api'); canAsync = !!cadesplugin.CreateObjectAsync; // signerOptions = cadesplugin.CAPICOM_CERTIFICATE_INCLUDE_WHOLE_CHAIN; signerOptions = cadesplugin.CAPICOM_CERTIFICATE_INCLUDE_END_ENTITY_ONLY; return new Promise(resolve => { if(!window.cadesplugin) { throw new Error('КриптоПро ЭЦП Browser plug-in не обнаружен'); } resolve(); }).then(() => { if(canAsync) { return cadesplugin.then(function(){ return cadesplugin.CreateObjectAsync("CAdESCOM.About"); }).then(function(oAbout){ return oAbout.Version; }).then(function(version) { pluginVersion = version; return { version, manifestV3: isManifestV3() }; }).catch(function(e) { // 'Плагин не загружен' const err = getError(e); throw new Error(err); }); } else { return new Promise(resolve => { try { const oAbout = cadesplugin.CreateObject("CAdESCOM.About"); if(!oAbout || !oAbout.Version) { throw new Error('КриптоПро ЭЦП Browser plug-in не загружен'); } pluginVersion = oAbout.Version; resolve({ version: pluginVersion, manifestV3: false }); } catch(e) { // 'Плагин не загружен' const err = getError(e); throw new Error(err); } }); } }); }; /** * Включает кеширование ПИНов от контейнеров чтоб не тробовать повторного ввода * возможно не поддерживается в ИЕ * @see https://www.cryptopro.ru/forum2/default.aspx?g=posts&t=10170 * @param {string} userPin не используется * @returns {Promise<boolean>} new binded state */ this.bind = function(userPin) { binded = true; return Promise.resolve(binded); }; /** * Заглушка для совместимости * @returns {Promise<boolean>} new binded state */ this.unbind = function() { binded = false; return Promise.resolve(binded); }; /** * Создание CSR. * @param {DN} dn * @param {string[]} ekuOids массив OID Extended Key Usage, по-умолчанию Аутентификация клиента '1.3.6.1.5.5.7.3.2' + Защищенная электронная почта '1.3.6.1.5.5.7.3.4' * @param {object} [options] * @param {string} [options.pin] * @param {int} [options.providerType] по умолчанию 80 (ГОСТ Р 34.10-2012) или 75 (ГОСТ Р 34.10-2001) * @returns {Promise<{ csr: string }>} объект с полями {csr: 'base64 запрос на сертификат'} * @see DN */ this.generateCSR = function(dn, ekuOids, options){ if(!ekuOids || !ekuOids.length) ekuOids = [ '1.3.6.1.5.5.7.3.2', // Аутентификация клиента '1.3.6.1.5.5.7.3.4' // Защищенная электронная почта ]; if (!options) options = {}; const pin = options.pin; const providerType = options.providerType || ProviderTypes.GOST_R_34_10_2012; if(canAsync) { let oEnroll, oRequest, oPrivateKey, oExtensions, oKeyUsage, oEnhancedKeyUsage, oEnhancedKeyUsageOIDs, aOIDs, oSstOID, oDn, oCspInformations, sCSPName, oSubjectSignTool; return cadesplugin.then(function(){ return Promise.all([ cadesplugin.CreateObjectAsync('X509Enrollment.CX509Enrollment'), // 0 cadesplugin.CreateObjectAsync('X509Enrollment.CX509CertificateRequestPkcs10'), // 1 cadesplugin.CreateObjectAsync('X509Enrollment.CX509PrivateKey'), // 2 cadesplugin.CreateObjectAsync('X509Enrollment.CX509ExtensionKeyUsage'), // 3 cadesplugin.CreateObjectAsync('X509Enrollment.CX509ExtensionEnhancedKeyUsage'), // 4 cadesplugin.CreateObjectAsync('X509Enrollment.CObjectIds'), // 5 cadesplugin.CreateObjectAsync('X509Enrollment.CX500DistinguishedName'), // 6 cadesplugin.CreateObjectAsync('X509Enrollment.CX509Extensions'), // 7 cadesplugin.CreateObjectAsync('X509Enrollment.CCspInformations'), // 8 cadesplugin.CreateObjectAsync('X509Enrollment.CX509Extension') //9 ]); }).then(function(objects){ oEnroll = objects[0]; oRequest = objects[1]; oPrivateKey = objects[2]; oKeyUsage = objects[3]; oEnhancedKeyUsage = objects[4]; oEnhancedKeyUsageOIDs = objects[5]; oDn = objects[6]; oExtensions = objects[7]; oCspInformations = objects[8]; oSubjectSignTool = objects[9]; return oCspInformations.AddAvailableCsps(); }).then(function(){ return oCspInformations.Count; }).then(function(cnt){ if(!cnt) throw new Error('No CSP informations!'); const aPromises = []; for(let i=0; i<cnt; i++) aPromises.push(oCspInformations.ItemByIndex(i)); return Promise.all(aPromises); }).then(function(aCspInformation){ const aPromises = []; for(let i in aCspInformation) { const a = aCspInformation[i]; aPromises.push(a.LegacyCsp); aPromises.push(a.Type); aPromises.push(a.Name); } return Promise.all(aPromises); }).then(function(aCspInfo){ let cspType, cspName; for(let i=0; i<aCspInfo.length; i+=3) { const bLegacyCsp = aCspInfo[i]; const nType = aCspInfo[i+1]; const sName = aCspInfo[i+2]; if(bLegacyCsp && nType == providerType) { cspType = nType; cspName = sCSPName = sName; break; } } if(!cspName || !cspType) { throw new Error('No suitable CSP!'); } const aPromises = [ oPrivateKey.propset_KeySpec(X509KeySpec.XCN_AT_SIGNATURE), oPrivateKey.propset_Existing(false), oPrivateKey.propset_ExportPolicy(X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_EXPORT_FLAG), oPrivateKey.propset_ProviderType(cspType), oPrivateKey.propset_ProviderName(cspName) ]; if(pin) aPromises.push(oPrivateKey.propset_Pin(pin)); return Promise.all(aPromises); }).then(function(){ return oRequest.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextUser, oPrivateKey, ''); }).then(function(){ return oKeyUsage.InitializeEncode( X509KeyUsageFlags.XCN_CERT_DIGITAL_SIGNATURE_KEY_USAGE | X509KeyUsageFlags.XCN_CERT_KEY_ENCIPHERMENT_KEY_USAGE | X509KeyUsageFlags.XCN_CERT_NON_REPUDIATION_KEY_USAGE | X509KeyUsageFlags.XCN_CERT_DATA_ENCIPHERMENT_KEY_USAGE ); }).then(function(){ const promises = []; for(let i=0; i<ekuOids.length; i++) { promises.push(cadesplugin.CreateObjectAsync('X509Enrollment.CObjectId')); } return Promise.all(promises); }).then(function(objects){ aOIDs = objects; const promises = []; for(let i=0; i<ekuOids.length; i++) { promises.push(aOIDs[i].InitializeFromValue(ekuOids[i])); } return Promise.all(promises); }).then(function(){ const promises = []; for(let i=0; i<ekuOids.length; i++) { promises.push(oEnhancedKeyUsageOIDs.Add(aOIDs[i])); } return Promise.all(promises); }).then(function(){ return cadesplugin.CreateObjectAsync('X509Enrollment.CObjectId'); }).then(function(oid){ oSstOID = oid; return oSstOID.InitializeFromValue('1.2.643.100.111'); // Subject Sign Tool }).then(function(){ const shortName = sCSPName.slice(0, maxLengthCSPName); const utf8arr = stringToUtf8ByteArray(shortName); utf8arr.unshift(asn1UTF8StringTag, utf8arr.length); const base64String = btoa(String.fromCharCode.apply(null, new Uint8Array(utf8arr))); //return oSubjectSignTool.Initialize(oSstOID, EncodingType.XCN_CRYPT_STRING_BINARY, utf8string); // не работает на винде return oSubjectSignTool.Initialize(oSstOID, EncodingType.XCN_CRYPT_STRING_BASE64, base64String); }).then(function(){ return oEnhancedKeyUsage.InitializeEncode(oEnhancedKeyUsageOIDs); }).then(function(){ return oRequest.X509Extensions; }).then(function(ext){ oExtensions = ext; return Promise.all([ oExtensions.Add(oKeyUsage), oExtensions.Add(oEnhancedKeyUsage), oExtensions.Add(oSubjectSignTool) ]); }).then(function(){ const strName = dnToX500DistinguishedName(dn); return oDn.Encode(strName, X500NameFlags.XCN_CERT_NAME_STR_ENABLE_PUNYCODE_FLAG); }).then(function(){ return oRequest.propset_Subject(oDn); }).then(function(){ return oEnroll.InitializeFromRequest(oRequest); }).then(function(){ return oEnroll.CreateRequest(EncodingType.XCN_CRYPT_STRING_BASE64); }).then(function(csr){ return { csr }; }).catch(function(e){ const err = getError(e); throw new Error(err); }); } else { return new Promise(resolve => { try { const oCspInformations = cadesplugin.CreateObject('X509Enrollment.CCspInformations'); const oEnroll = cadesplugin.CreateObject('X509Enrollment.CX509Enrollment'); const oRequest = cadesplugin.CreateObject('X509Enrollment.CX509CertificateRequestPkcs10'); const oPrivateKey = cadesplugin.CreateObject('X509Enrollment.CX509PrivateKey'); const oKeyUsage = cadesplugin.CreateObject('X509Enrollment.CX509ExtensionKeyUsage'); const oEnhancedKeyUsage = cadesplugin.CreateObject('X509Enrollment.CX509ExtensionEnhancedKeyUsage'); const oEnhancedKeyUsageOIDs = cadesplugin.CreateObject('X509Enrollment.CObjectIds'); const oDn = cadesplugin.CreateObject('X509Enrollment.CX500DistinguishedName'); let cspType, cspName; oCspInformations.AddAvailableCsps(); for(let i=0; i<oCspInformations.Count; i++) { const oCspInfo = oCspInformations.ItemByIndex(i); if(oCspInfo.LegacyCsp && oCspInfo.Type == providerType) { cspType = oCspInfo.Type; cspName = oCspInfo.Name; break; } } if(!cspName || !cspType) { throw new Error('No suitable CSP!'); } oPrivateKey.KeySpec = X509KeySpec.XCN_AT_SIGNATURE; oPrivateKey.Existing = false; oPrivateKey.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_EXPORT_FLAG; oPrivateKey.ProviderName = cspName; oPrivateKey.ProviderType = cspType; // под виндой нельзя задать ПИН тк не дает доступа //oPrivateKey.Pin = pin; //CX509PrivateKey::put_Pin: Access is denied. 0x80070005 (WIN32: 5) oRequest.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextUser, oPrivateKey, ''); oKeyUsage.InitializeEncode( X509KeyUsageFlags.XCN_CERT_DIGITAL_SIGNATURE_KEY_USAGE | X509KeyUsageFlags.XCN_CERT_KEY_ENCIPHERMENT_KEY_USAGE | X509KeyUsageFlags.XCN_CERT_NON_REPUDIATION_KEY_USAGE | X509KeyUsageFlags.XCN_CERT_DATA_ENCIPHERMENT_KEY_USAGE ); const aEnhancedKeyUsageOIDs = []; for(let i=0; i<ekuOids.length; i++) { aEnhancedKeyUsageOIDs.push(cadesplugin.CreateObject('X509Enrollment.CObjectId')); aEnhancedKeyUsageOIDs[i].InitializeFromValue(ekuOids[i]); oEnhancedKeyUsageOIDs.Add(aEnhancedKeyUsageOIDs[i]); } oEnhancedKeyUsage.InitializeEncode(oEnhancedKeyUsageOIDs); oRequest.X509Extensions.Add(oKeyUsage); oRequest.X509Extensions.Add(oEnhancedKeyUsage); //subject sign tool const ssOID = cadesplugin.CreateObject('X509Enrollment.CObjectId'); ssOID.InitializeFromValue('1.2.643.100.111'); const shortName = cspName.slice(0, maxLengthCSPName); const utf8arr = stringToUtf8ByteArray(shortName); utf8arr.unshift(asn1UTF8StringTag, shortName.length); const base64String = btoa(String.fromCharCode.apply(null, new Uint8Array(utf8arr))); const oSubjectSignTool = cadesplugin.CreateObject('X509Enrollment.CX509Extension'); oSubjectSignTool.Initialize(ssOID, EncodingType.XCN_CRYPT_STRING_BASE64, base64String); oRequest.X509Extensions.Add(oSubjectSignTool); const strName = dnToX500DistinguishedName(dn); oDn.Encode(strName, X500NameFlags.XCN_CERT_NAME_STR_ENABLE_PUNYCODE_FLAG); oRequest.Subject = oDn; oEnroll.InitializeFromRequest(oRequest); const csr = oEnroll.CreateRequest(EncodingType.XCN_CRYPT_STRING_BASE64); resolve({ csr }); } catch(e) { const err = getError(e); throw new Error(err); } }); } }; /** * Запись сертификата. * @param {string} certBase64 * @returns {Promise<string>} thumbprint */ this.writeCertificate = function(certBase64){ if(canAsync) { let oEnroll, existingSha = []; return this.listCertificates().then(function(certs){ for(let i in certs) { existingSha.push(certs[i].id); } return cadesplugin.CreateObjectAsync('X509Enrollment.CX509Enrollment'); }).then(function(enroll){ oEnroll = enroll; return oEnroll.Initialize(X509CertificateEnrollmentContext.ContextUser); }).then(function(){ return oEnroll.InstallResponse(InstallResponseRestrictionFlags.AllowNone, certBase64, EncodingType.XCN_CRYPT_STRING_BASE64, ''); }).then(this.listCertificates).then(function(certs){ for(let i in certs) { const sha = certs[i].id; if(existingSha.indexOf(sha) < 0) { return sha; } } throw new Error('Не удалось найти установленный сертификат по отпечатку'); }).catch(function(e){ const err = getError(e); throw new Error(err); }); } else { return new Promise(resolve => { try { const existingSha = []; let oStore = cadesplugin.CreateObject("CAPICOM.Store"); oStore.Open(cadesplugin.CAPICOM_CURRENT_USER_STORE, cadesplugin.CAPICOM_MY_STORE, cadesplugin.CAPICOM_STORE_OPEN_MAXIMUM_ALLOWED); let oCertificates = oStore.Certificates; for(let i=1; i<=oCertificates.Count; i++) { existingSha.push(oCertificates.Item(i).Thumbprint); } oStore.Close(); const oEnroll = cadesplugin.CreateObject('X509Enrollment.CX509Enrollment'); oEnroll.Initialize(X509CertificateEnrollmentContext.ContextUser); oEnroll.InstallResponse(InstallResponseRestrictionFlags.AllowNone, certBase64, EncodingType.XCN_CRYPT_STRING_BASE64, ''); oStore = cadesplugin.CreateObject("CAPICOM.Store"); oStore.Open(cadesplugin.CAPICOM_CURRENT_USER_STORE, cadesplugin.CAPICOM_MY_STORE, cadesplugin.CAPICOM_STORE_OPEN_MAXIMUM_ALLOWED); oCertificates = oStore.Certificates; let found = false; let sha = ''; for(let i=1; i<=oCertificates.Count; i++) { sha = oCertificates.Item(i).Thumbprint; if(existingSha.indexOf(sha) < 0) { found = true; } } oStore.Close(); if(found) { resolve(sha); } else { throw new Error('Не удалось найти установленный сертификат по отпечатку'); } } catch(e) { const err = getError(e); throw new Error(err); } }); } }; /** * Получение информации о сертификате. * @param {string} certThumbprint * @param {object} [options] * @param {boolean} [options.checkValid] проверять валидность сертификата через СКЗИ, а не сроку действия * @returns {Promise<Object>} */ this.certificateInfo = function(certThumbprint, options){ if (!options) options = { checkValid: false }; const infoToString = function () { return 'Название: ' + this.Name + '\nИздатель: ' + this.IssuerName + '\nСубъект: ' + this.SubjectName + '\nВерсия: ' + this.Version + '\nАлгоритм: ' + this.Algorithm + // PublicKey Algorithm '\nСерийный №: ' + this.SerialNumber + '\nОтпечаток SHA1: ' + this.Thumbprint + '\nНе действителен до: ' + this.ValidFromDate + '\nНе действителен после: ' + this.ValidToDate + '\nПриватный ключ: ' + (this.HasPrivateKey ? 'Есть' : 'Нет') + '\nКриптопровайдер: ' + this.ProviderName + // PrivateKey ProviderName '\nВалидный: ' + (this.IsValid ? 'Да' : 'Нет'); }; if(canAsync) { let oInfo = {}; return getCertificateObject(certThumbprint) .then(oCertificate => Promise.all([ oCertificate.HasPrivateKey(), options.checkValid ? oCertificate.IsValid().then(v => v.Result) : undefined, oCertificate.IssuerName, oCertificate.SerialNumber, oCertificate.SubjectName, oCertificate.Thumbprint, oCertificate.ValidFromDate, oCertificate.ValidToDate, oCertificate.Version, oCertificate.PublicKey().then(k => k.Algorithm).then(a => a.FriendlyName), oCertificate.HasPrivateKey().then(key => !key && ['', undefined] || oCertificate.PrivateKey.then(k => Promise.all([ k.ProviderName, k.ProviderType ]))) ])) .then(a => { oInfo = { HasPrivateKey: a[0], IsValid: a[1], IssuerName: a[2], Issuer: undefined, SerialNumber: a[3], SubjectName: a[4], Subject: undefined, Name: undefined, Thumbprint: a[5], ValidFromDate: new Date(a[6]), ValidToDate: new Date(a[7]), Version: a[8], Algorithm: a[9], ProviderName: a[10][0], ProviderType: a[10][1] }; oInfo.Subject = string2dn(oInfo.SubjectName); oInfo.Issuer = string2dn(oInfo.IssuerName); oInfo.Name = oInfo.Subject['CN']; if (!options.checkValid) { const dt = new Date(); oInfo.IsValid = dt >= oInfo.ValidFromDate && dt <= oInfo.ValidToDate; } oInfo.toString = infoToString; return oInfo; }) .catch(e => { const err = getError(e); throw new Error(err); }); } else { return new Promise(resolve => { try { const oCertificate = getCertificateObject(certThumbprint); const hasKey = oCertificate.HasPrivateKey(); const oParesedSubj = string2dn(oCertificate.SubjectName); const oInfo = { HasPrivateKey: hasKey, IsValid: options.checkValid ? oCertificate.IsValid().Result : undefined, IssuerName: oCertificate.IssuerName, Issuer: string2dn(oCertificate.IssuerName), SerialNumber: oCertificate.SerialNumber, SubjectName: oCertificate.SubjectName, Subject: oParesedSubj, Name: oParesedSubj['CN'], Thumbprint: oCertificate.Thumbprint, ValidFromDate: new Date(oCertificate.ValidFromDate), ValidToDate: new Date(oCertificate.ValidToDate), Version: oCertificate.Version, Algorithm: oCertificate.PublicKey().Algorithm.FriendlyName, ProviderName: hasKey && oCertificate.PrivateKey.ProviderName || '', ProviderType: hasKey && oCertificate.PrivateKey.ProviderType || undefined, }; if (!options.checkValid) { const dt = new Date(); oInfo.IsValid = dt >= oInfo.ValidFromDate && dt <= oInfo.ValidToDate; } oInfo.toString = infoToString; resolve(oInfo); } catch (e) { const err = getError(e); throw new Error(err); } }); } }; /** * Получение массива доступных сертификатов * @returns {Promise<{id: string; name: string; subject: DN; validFrom: Date; validTo: Date;}[]>} [{id, name, subject, validFrom, validTo}, ...] */ this.listCertificates = function(){ const tryContainerStore = hasContainerStore(); if(canAsync) { let oStore, ret; return cadesplugin.then(function(){ return cadesplugin.CreateObjectAsync("CAPICOM.Store"); }).then(store => { oStore = store; return oStore.Open(cadesplugin.CAPICOM_CURRENT_USER_STORE, cadesplugin.CAPICOM_MY_STORE, cadesplugin.CAPICOM_STORE_OPEN_MAXIMUM_ALLOWED); }).then(() => { return fetchCertsFromStore(oStore); }).then(certs => { ret = certs; return oStore.Close(); }).then(() => { if (tryContainerStore) { let certificates; return oStore.Open(cadesplugin.CADESCOM_CONTAINER_STORE).then(() => { const skipIds = ret.map(a => a.id); return fetchCertsFromStore(oStore, skipIds); }).then(certs => { certificates = certs; return oStore.Close(); }).then(() => { return certificates; }).catch(e => { console.log(e); return []; }); } else { return []; } }).then(certs => { ret.push(...certs); return ret; }).catch(e => { const err = getError(e); throw new Error(err); }); } else { return new Promise(resolve => { try { const oStore = cadesplugin.CreateObject("CAPICOM.Store"); oStore.Open(cadesplugin.CAPICOM_CURRENT_USER_STORE, cadesplugin.CAPICOM_MY_STORE, cadesplugin.CAPICOM_STORE_OPEN_MAXIMUM_ALLOWED); const ret = fetchCertsFromStore(oStore); oStore.Close(); if (tryContainerStore) { try { oStore.Open(cadesplugin.CADESCOM_CONTAINER_STORE); const skipIds = ret.map(a => a.id); const certs = fetchCertsFromStore(oStore, skipIds); oStore.Close(); ret.push(...certs); } catch (e) { console.log(e); } } resolve(ret); } catch (e) { const err = getError(e); throw new Error(err); } }); } }; /** * Чтение сертификата * @param {string} certThumbprint * @returns {Promise<string>} base64 */ this.readCertificate = function(certThumbprint){ if(canAsync) { return getCertificateObject(certThumbprint) .then(cert => cert.Export(cadesplugin.CADESCOM_ENCODE_BASE64)) .catch(e => { const err = getError(e); throw new Error(err); }); } else { return new Promise(resolve => { try { const oCertificate = getCertificateObject(certThumbprint); const data = oCertificate.Export(cadesplugin.CADESCOM_ENCODE_BASE64); resolve(data); } catch (e) { const err = getError(e); throw new Error(err); } }); } }; /** * Подпись данных отсоединенная или присоединенная * @param {string} dataBase64 * @param {string} certThumbprint * @param {object} [options] * @param {string} [options.pin] будет запрошен, если отсутствует * @param {boolean} [options.attached] присоединенная подпись * @returns {Promise<string>} base64 */ this.signData = function(dataBase64, certThumbprint, options){ if (typeof options === 'string') { // обратная совместимость с версией 2.3 options = { pin: options }; } if (!options) options = {}; const { pin, attached } = options; if(canAsync) { let oCertificate, oSigner, oSignedData; return getCertificateObject(certThumbprint, pin) .then(certificate => { oCertificate = certificate; return Promise.all([ cadesplugin.CreateObjectAsync("CAdESCOM.CPSigner"), cadesplugin.CreateObjectAsync("CAdESCOM.CadesSignedData") ]); }) .then(objects => { oSigner = objects[0]; oSignedData = objects[1]; return Promise.all([ oSigner.propset_Certificate(oCertificate), oSigner.propset_Options(signerOptions), // Значение свойства ContentEncoding должно быть задано до заполнения свойства Content oSignedData.propset_ContentEncoding(cadesplugin.CADESCOM_BASE64_TO_BINARY) ]); }) .then(() => oSignedData.propset_Content(dataBase64)) .then(() => oSignedData.SignCades(oSigner, cadesplugin.CADESCOM_CADES_BES, !attached)) .catch(e => { const err = getError(e); throw new Error(err); }); } else { return new Promise(resolve => { try { const oCertificate = getCertificateObject(certThumbprint, pin); const oSigner = cadesplugin.CreateObject("CAdESCOM.CPSigner"); oSigner.Certificate = oCertificate; oSigner.Options = signerOptions; const oSignedData = cadesplugin.CreateObject("CAdESCOM.CadesSignedData"); // Значение свойства ContentEncoding должно быть задано до заполнения свойства Content oSignedData.ContentEncoding = cadesplugin.CADESCOM_BASE64_TO_BINARY; oSignedData.Content = dataBase64; const sSignedMessage = oSignedData.SignCades(oSigner, cadesplugin.CADESCOM_CADES_BES, !attached); resolve(sSignedMessage); } catch (e) { const err = getError(e); throw new Error(err); } }); } }; /** * Совместная подпись данных (двумя сертификатами). * @param {string} dataBase64 * @param {string} certThumbprint SHA1 отпечаток первого сертификата * @param {string} certThumbprint2 SHA1 отпечаток второго сертификата * @param {object} [options] * @param {string} [options.pin] будет запрошен, если отсутствует * @param {string} [options.pin2] будет запрошен, если отсутствует * @param {boolean} [options.attached] присоединенная подпись * @returns {Promise<string>} base64 */ this.signData2 = function(dataBase64, certThumbprint, certThumbprint2, options){ if (!options) options = {}; const { pin, pin2, attached } = options; if(canAsync) { let oCertificate, oCertificate2, oSigner, oSignedData; return Promise.all([ getCertificateObject(certThumbprint, pin), getCertificateObject(certThumbprint2, pin2) ]) .then(certs => { oCertificate = certs[0]; oCertificate2 = certs[1]; return Promise.all([ cadesplugin.CreateObjectAsync("CAdESCOM.CPSigner"), cadesplugin.CreateObjectAsync("CAdESCOM.CadesSignedData") ]); }) .then(objects => { oSigner = objects[0]; oSignedData = objects[1]; // Значение свойства ContentEncoding должно быть задано до заполнения свойства Content return oSignedData.propset_ContentEncoding(cadesplugin.CADESCOM_BASE64_TO_BINARY); }) .then(() => oSignedData.propset_Content(dataBase64)) .then(() => Promise.all([ oSigner.propset_Certificate(oCertificate), oSigner.propset_Options(signerOptions) ])) .then(() => oSignedData.SignCades(oSigner, cadesplugin.CADESCOM_CADES_BES, !attached)) .then(() => Promise.all([ oSigner.propset_Certificate(oCertificate2), oSigner.propset_Options(signerOptions) ])) .then(() => oSignedData.CoSignCades(oSigner, cadesplugin.CADESCOM_CADES_BES)) .catch(e => { console.log(arguments); const err = getError(e); throw new Error(err); }); } else { return new Promise(resolve => { try { const oCertificate = getCertificateObject(certThumbprint, pin); const oCertificate2 = getCertificateObject(certThumbprint2, pin2); const oSignedData = cadesplugin.CreateObject("CAdESCOM.CadesSignedData"); // Значение свойства ContentEncoding должно быть задано до заполнения свойства Content oSignedData.ContentEncoding = cadesplugin.CADESCOM_BASE64_TO_BINARY; oSignedData.Content = dataBase64; const oSigner = cadesplugin.CreateObject("CAdESCOM.CPSigner"); oSigner.Certificate = oCertificate; oSigner.Options = signerOptions; const sSignedMessage = oSignedData.SignCades(oSigner, cadesplugin.CADESCOM_CADES_BES, !attached); oSigner.Certificate = oCertificate2; oSigner.Options = signerOptions; const sSignedMessage2 = oSignedData.CoSignCades(oSigner, cadesplugin.CADESCOM_CADES_BES); resolve(sSignedMessage2); } catch (e) { const err = getError(e); throw new Error(err); } }); } }; /** * Добавить подпись к существующей. * @param {string} dataBase64 игнорируется если прикрепленная подпись * @param {string} signBase64 существующая подпись * @param {string} certThumbprint SHA1 отпечаток первого сертификата * @param {object} [options] * @param {string} [options.pin] будет запрошен, если отсутствует * @param {boolean} [options.attached] присоединенная подпись * @returns {Promise<string>} base64 */ this.addSign = function(dataBase64, signBase64, certThumbprint, options){ if (!options) options = {}; const { pin, attached } = options; if(canAsync) { let oCertificate, oSigner, oSignedData; return getCertificateObject(certThumbprint, pin) .then(certificate => { oCertificate = certificate; return Promise.all([ cadesplugin.CreateObjectAsync("CAdESCOM.CPSigner"), cadesplugin.CreateObjectAsync("CAdESCOM.CadesSignedData") ]); }) .then(objects => { oSigner = objects[0]; oSignedData = objects[1]; if (attached) { return Promise.resolve(); } else { // Значение свойства ContentEncoding должно быть задано до заполнения свойства Content return oSignedData.propset_ContentEncoding(cadesplugin.CADESCOM_BASE64_TO_BINARY) .then(() => oSignedData.propset_Content(dataBase64)); } }) .then(() => { return oSignedData.VerifyCades(signBase64, cadesplugin.CADESCOM_CADES_BES, !attached).catch(function(e){ console.log('Existing sign not verified: %o', e); // Для создания второй подписи успешная проверка не требуется. // Вы можете перехватить исключение при проверке, и добавить подпись вторую. // Проверка нужна только для того что бы подпись попала внутрь SignedData. }); }) .then(() => Promise.all([ oSigner.propset_Certificate(oCertificate), oSigner.propset_Options(signerOptions) ])) .then(() => oSignedData.CoSignCades(oSigner, cadesplugin.CADESCOM_CADES_BES)) .catch(e => { const err = getError(e); throw new Error(err); }); } else { return new Promise(resolve => { try { const oCertificate = getCertificateObject(certThumbprint, pin); const oSignedData = cadesplugin.CreateObject("CAdESCOM.CadesSignedData"); if (!attached) { // Значение свойства ContentEncoding должно быть задано до заполнения свойства Content oSignedData.ContentEncoding = cadesplugin.CADESCOM_BASE64_TO_BINARY; oSignedData.Content = dataBase64; } try { oSignedData.VerifyCades(signBase64, cadesplugin.CADESCOM_CADES_BES, !attached); } catch(e) { console.log('Existing sign not verified: %o', e); // Для создания второй подписи успешная проверка не требуется. // Вы можете перехватить исключение при проверке, и добавить подпись вторую. // Проверка нужна только для того что бы подпись попала внутрь SignedData. } const oSigner = cadesplugin.CreateObject("CAdESCOM.CPSigner"); oSigner.Certificate = oCertificate; oSigner.Options = signerOptions; const sSignedMessage = oSignedData.CoSignCades(oSigner, cadesplugin.CADESCOM_CADES_BES); resolve(sSignedMessage); } catch (e) { const err = getError(e); throw new Error(err); } }); } }; /** * Проверить подпись. * @param {string} dataBase64 игнорируется если прикрепленная подпись * @param {string} signBase64 существующая подпись * @param {object} [options] * @param {boolean} [options.attached] присоединенная подпись * @returns {Promise<boolean>} true или reject */ this.verifySign = function(dataBase64, signBase64, options){ if (!options) options = {}; const { attached } = options; if(canAsync) { let oSignedData; return cadesplugin.then(function(){ return cadesplugin.CreateObjectAsync("CAdESCOM.CadesSignedData"); }).then(function(object){ oSignedData = object; if (attached) { return Promise.resolve(); } else { // Значение свойства ContentEncoding должно быть задано до заполнения свойства Content return oSignedData.propset_ContentEncoding(cadesplugin.CADESCOM_BASE64_TO_BINARY) .then(() => oSignedData.propset_Content(dataBase64)); } }).then(function(){ return oSignedData.VerifyCades(signBase64, cadesplugin.CADESCOM_CADES_BES, !attached); }).then(function(){ //console.log('sign2: %s', sign2); return true; }).catch(function(e){ const err = getError(e); throw new Error(err); }); } else { return new Promise(resolve => { try { const oSignedData = cadesplugin.CreateObject("CAdESCOM.CadesSignedData"); if (!attached) { // Значение свойства ContentEncoding должно быть задано до заполнения свойства Content oSignedData.ContentEncoding = cadesplugin.CADESCOM_BASE64_TO_BINARY; oSignedData.Content = dataBase64; } oSignedData.VerifyCades(signBase64, cadesplugin.CADESCOM_CADES_BES, !attached); resolve(true); } catch (e) { const err = getError(e); throw new Error(err); } }); } }; /** * Шифрование данных * @param {string} dataBase64 данные в base64 * @param {string} certThumbprint SHA1 отпечаток сертификата * @returns {Promise<string>} base64 enveloped data */ this.encryptData = function(dataBase64, certThumbprint) { if(canAsync) { let oCertificate, oEnvelop, oRecipients; return getCertificateObject(certThumbprint) .then(certificate => { oCertificate = certificate; return cadesplugin.CreateObjectAsync("CAdESCOM.CPEnvelopedData"); }) .then(envelop => { oEnvelop = envelop; // Значение свойства ContentEncoding должно быть задано до заполнения свойства Content return oEnvelop.propset_ContentEncoding(cadesplugin.CADESCOM_BASE64_TO_BINARY); }) .then(() => oEnvelop.propset_Content(dataBase64)) .then(() => oEnvelop.Recipients) .then(recipients => { oRecipients = recipients; return oRecipients.Clear(); }) .then(() => oRecipients.Add(oCertificate)) .then(() => oEnvelop.Encrypt()) .catch(e => { const err = getError(e); throw new Error(err); }); } else { return new Promise(resolve => { try { const oCertificate = getCertificateObject(certThumbprint); const oEnvelop = cadesplugin.CreateObject("CAdESCOM.CPEnvelopedData"); oEnvelop.ContentEncoding = cadesplugin.CADESCOM_BASE64_TO_BINARY; oEnvelop.Content = dataBase64; oEnvelop.Recipients.Clear(); oEnvelop.Recipients.Add(oCertificate); const encryptedData = oEnvelop.Encrypt(); resolve(encryptedData); } catch (e) { const err = getError(e); throw new Error(err); } }); } }; /** * Дешифрование данных * @param {string} dataBase64 данные в base64 * @param {string} certThumbprint SHA1 отпечаток сертификата * @param {string} pin будет запрошен, если отсутствует * @returns {Promise<string>} base64 */ this.decryptData = function(dataBase64, certThumbprint, pin) { if(canAsync) { let oCertificate, oEnvelop, oRecipients; return getCertificateObject(certThumbprint, pin) .then(certificate => { oCertificate = certificate; return cadesplugin.CreateObjectAsync("CAdESCOM.CPEnvelopedData"); }) .then(envelop => { oEnvelop = envelop; // Значение свойства ContentEncoding должно быть задано до заполнения свойства Content return oEnvelop.propset_ContentEncoding(cadesplugin.CADESCOM_BASE64_TO_BINARY); }) // .then(() => oEnvelop.propset_Content(dataBase64)) .then(() => oEnvelop.Recipients) .then(recipients => { oRecipients = recipients; return oRecipients.Clear(); }) .then(() => oRecipients.Add(oCertificate)) .then(() => oEnvelop.Decrypt(dataBase64)) .then(() => oEnvelop.Content) .catch(e => { const err = getError(e); throw new Error(err); }); } else { return new Promise(resolve => { try { const oCertificate = getCertificateObject(certThumbprint, pin); const oEnvelop = cadesplugin.CreateObject("CAdESCOM.CPEnvelopedData"); oEnvelop.ContentEncoding = cadesplugin.CADESCOM_BASE64_TO_BINARY; // oEnvelop.Content = dataBase64; oEnvelop.Recipients.Clear(); oEnvelop.Recipients.Add(oCertificate); oEnvelop.Decrypt(dataBase64); resolve(oEnvelop.Content); } catch (e) { const err = getError(e); throw new Error(err); } }); } }; function hasContainerStore() { // В версии плагина 2.0.13292+ есть возможность получить сертификаты из // закрытых ключей и не установленных в хранилище // но не смотря на это, все равно приходится собирать список сертификатов // старым и новым способом тк в новом будет отсутствовать часть старого // предположительно ГОСТ-2001 с какими-то определенными Extended Key Usage OID return versionCompare(pluginVersion, '2.0.13292') >= 0; } function isManifestV3() { // Начиная с КриптоПро ЭЦП Browser plug-in версии 2.0.15400 (от 03.04.2025), // реализована поддержка нового manifest V3 расширения Extension for CAdES Browser Plug-in return versionCompare(pluginVersion, '2.0.15400') >= 0; } function fetchCertsFromStore(oStore, skipIds = []) { if (canAsync) { let oCertificates; return oStore.Certificates.then(certificates => { oCertificates = certificates; return certificates.Count; }).then(count => { const certs = []; for (let i = 1; i <= count; i++) certs.push(oCertificates.Item(i)); return Promise.all(certs); }).then(certificates => { const certs = []; for (let i in certificates) certs.push( certificates[i].SubjectName, certificates[i].Thumbprint, certificates[i].ValidFromDate, certificates[i].ValidToDate ); return Promise.all(certs); }).then(data => { const certs = []; for (let i = 0; i < data.length; i += 4) { const id = data[i + 1]; if (skipIds.indexOf(id) + 1) break; const oDN = string2dn(data[i]); certs.push({ id, name: formatCertificateName(oDN), subject: oDN, validFrom: new Date(data[i + 2]), validTo: new Date(data[i + 3]) }); } return certs; }); } else { const oCertificates = oStore.Certificates; const certs = []; for (let i = 1; i <= oCertificates.Count; i++) { const oCertificate = oCertificates.Item(i); const id = oCertificate.Thumbprint; if (skipIds.indexOf(id) + 1) break; const oDN = string2dn(oCertificate.SubjectName); certs.push({ id, name: formatCertificateName(oDN), subject: oDN, validFrom: new Date(oCertificate.ValidFromDate), validTo: new Date(oCertificate.ValidToDate) }); } return certs; } } function findCertInStore(oStore, certThumbprint) { if(canAsync) { return oStore.Certificates .then(certificates => certificates.Find(cadesplugin.CAPICOM_CERTIFICATE_FIND_SHA1_HASH, certThumbprint)) .then(certificates => certificates.Count.then(count => { if (count === 1) { return certificates.Item(1); } else { return null; } })); } else { const oCertificates = oStore.Certificates.Find(cadesplugin.CAPICOM_CERTIFICATE_FIND_SHA1_HASH, certThumbprint); if (oCertificates.Count === 1) { return oCertificates.Item(1); } else { return null; } } } function getCertificateObject(certThumbprint, pin) { if(canAsync) { let oStore, oCertificate; return cadesplugin .then(() => cadesplugin.CreateObjectAsync("CAPICOM.Store")) //TODO: CADESCOM.Store ? .then(o => { oStore = o; return oStore.Open(cadesplugin.CAPICOM_CURRENT_USER_STORE, cadesplugin.CAPICOM_MY_STORE, cadesplugin.CAPICOM_STORE_OPEN_MAXIMUM_ALLOWED); }) .then(() => findCertInStore(oStore, certThumbprint)) .then(cert => oStore.Close().then(() => { if (!cert && hasContainerStore()) return oStore.Open(cadesplugin.CADESCOM_CONTAINER_STORE) .then(() => findCertInStore(oStore, certThumbprint)) .then(c => oStore.Close().then(() => c)); else return cert; })) .then(certificate => { if(!certificate) { throw new Error("Не обнаружен сертификат c отпечатком " + certThumbprint); } return oCertificate = certificate; }) .then(() => oCertificate.HasPrivateKey()) .then(hasKey => { let p = Promise.resolve(); if (hasKey && pin) { p = p.then(() => oCertificate.PrivateKey).then(privateKey => Promise.all([ privateKey.propset_KeyPin(pin ? pin : ''), privateKey.propset_CachePin(binded) ])); } return p; }) .then(() => oCertificate); } else { let oCertificate; const oStore = cadesplugin.CreateObject("CAPICOM.Store"); oStore.Open(cadesplugin.CAPICOM_CURRENT_USER_STORE, cadesplugin.CAPICOM_MY_STORE, cadesplugin.CAPICOM_STORE_OPEN_MAXIMUM_ALLOWED); oCertificate = findCertInStore(oStore, certThumbprint); oStore.Close(); if (!oCertificate && hasContainerStore()) { oStore.Open(cadesplugin.CADESCOM_CONTAINER_STORE); oCertificate = findCertInStore(oStore, certThumbprint); oStore.Close(); } if(!oCertificate) { throw new Error("Не обнаружен сертификат c отпечатком " + certThumbprint); } if (oCertificate.HasPrivateKey && pin) { oCertificate.PrivateKey.KeyPin = pin ? pin : ''; if(oCertificate.PrivateKey.CachePin !== undefined) { // возможно не поддерживается в ИЕ // https://www.cryptopro.ru/forum2/default.aspx?g=posts&t=10170 oCertificate.PrivateKey.CachePin = binded; } } return oCertificate; } } /** * Получить текст ошибки * @param {Error} e * @returns {string} */ function getError(e) { console.log('Crypto-Pro error', e.message || e); if(e.message) { for(var i in cadesErrorMesages) { if(cadesErrorMesages.hasOwnProperty(i)) { if(e.message.indexOf(i) + 1) { e.message = cadesErrorMesages[i]; break; } } } } return e.message || e; } /** * Разобрать субъект в объект DN * @param {string} subjectName * @returns {DN} */ function string2dn(subjectName) { const dn = new DN; let pairs = subjectName.match(/([а-яёА-ЯЁa-zA-Z0-9\.\s]+)=(?:("[^"]+?")|(.+?))(?:,|$)/g); if (pairs) pairs = pairs.map(el => el.replace(/,$/, '')); else pairs = []; //todo: return null? pairs.forEach(pair => { const d = pair.match(/([^=]+)=(.*)/); if (d && d.length === 3) { const rdn = d[1].trim().replace(/^OID\./, ''); dn[rdn] = d[2].trim() .replace(/^"(.*)"$/, '$1') .replace(/""/g, '"'); } }); return convertDN(dn); } /** * Собрать DN в строку пригодную для CX500DistinguishedName.Encode * @see https://docs.microsoft.com/en-us/windows/win32/api/certenroll/nf-certenroll-ix500distinguishedname-encode * @see https://www.cryptopro.ru/sites/default/files/products/cades/demopage/async_code.js * @see https://testgost2012.cryptopro.ru/certsrv/async_code.js * @param {DN} dn * @returns {string} */ function dnToX500DistinguishedName(dn) { let ret = ''; for (let i in dn) { if (dn.hasOwnProperty(i)) { ret += i + '="' + dn[i].replace(/"/g, '""') + '", '; } } return ret; } /** * Получить название сертификата * @param {DN} o объект, включающий в себя значения субъекта сертификата * @see convertDN * @returns {String} */ function formatCertificateName(o) { return '' + o['CN'] + (o['INNLE'] ? '; ИНН ЮЛ ' + o['INNLE'] : '') + (o['INN'] ? '; ИНН ' + o['INN'] : '') + (o['SNILS'] ? '; СНИЛС ' + o['SNILS'] : ''); } /** * https://stackoverflow.com/questions/18729405/how-to-convert-utf8-string-to-byte-array/28227607#28227607 * @param {string} str * @returns {Array} */ function stringToUtf8ByteArray(str) { // TODO(user): Use native implementations if/when available var out = [], p = 0; for (var i = 0; i < str.length; i++) { var c = str.charCodeAt(i); if (c < 128) { out[p++] = c; } else if (c < 2048) { out[p++] = (c >> 6) | 192; out[p++] = (c & 63) | 128; } else if ( ((c & 0xFC00) == 0xD800) && (i + 1) < str.length && ((str.charCodeAt(i + 1) & 0xFC00) == 0xDC00)) { // Surrogate Pair c = 0x10000 + ((c & 0x03FF) << 10) + (str.charCodeAt(++i) & 0x03FF); out[p++] = (c >> 18) | 240; out[p++] = ((c >> 12) & 63) | 128; out[p++] = ((c >> 6) & 63) | 128; out[p++] = (c & 63) | 128; } else { out[p++] = (c >> 12) | 224; out[p++] = ((c >> 6) & 63) | 128; out[p++] = (c & 63) | 128; } } return out; } } export default CryptoPro;