From e456be7edafec8972a1b9284fbefecd9d8045136 Mon Sep 17 00:00:00 2001 From: vgoma Date: Tue, 5 May 2020 09:56:07 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=20=D0=B2=D1=8B=D1=87=D0=B8=D1=81?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=85=D1=8D=D1=88=D0=B0,=20?= =?UTF-8?q?=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8=D0=BB=20=D0=BF=D1=80=D0=B8?= =?UTF-8?q?=D0=BC=D0=B5=D1=80=20=D1=81=20=D1=82=D1=8D=D0=B3=D0=BE=D0=BC=20?= =?UTF-8?q?script?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/script-tag/public/cert-list.js | 31 +-- examples/script-tag/public/create-sign.js | 54 +++-- examples/script-tag/public/get-env-info.js | 17 +- examples/script-tag/public/index.html | 79 ++++++- .../polyfills/details-element-polyfill.js | 194 ++++++++++++++++++ examples/script-tag/public/show-cert.js | 33 ++- src/api/createHash.ts | 71 +++++++ src/api/createSignature.ts | 6 +- src/api/index.ts | 1 + 9 files changed, 434 insertions(+), 52 deletions(-) create mode 100644 examples/script-tag/public/polyfills/details-element-polyfill.js create mode 100644 src/api/createHash.ts diff --git a/examples/script-tag/public/cert-list.js b/examples/script-tag/public/cert-list.js index ab2283c..1467ada 100644 --- a/examples/script-tag/public/cert-list.js +++ b/examples/script-tag/public/cert-list.js @@ -4,21 +4,28 @@ ;(function () { 'use strict'; - var $certs = document.getElementById('certList'), - $errorMsg = document.getElementById('errorMessage'); + var $certificate = document.getElementById('certificate'), + $createSignature = document.getElementById('createSignature'), + $certificateDetails = document.getElementById('certificateDetails'), + $certificateError = document.getElementById('certificateError'); - window.cryptoPro.getUserCertificates() - .then(function (certificateList) { - certificateList.forEach(function (certificate) { - var $certOption = document.createElement('option'); + $certificate.addEventListener('change', function handleCertSelection() { + var thumbprint = $certificate.value; - $certOption.textContent = certificate.name + ' (действителен до: ' + certificate.validTo + ')'; + $createSignature.disabled = !thumbprint; + $certificateDetails.style.display = thumbprint ? 'block' : 'none'; + }); - $certOption.value = certificate.thumbprint; + window.cryptoPro.getUserCertificates().then(function (certificateList) { + certificateList.forEach(function (certificate) { + var $certOption = document.createElement('option'); - $certs.appendChild($certOption); - }); - }, function (error) { - $errorMsg.textContent = '\n' + error.message; + $certOption.textContent = certificate.name + ' (действителен до: ' + certificate.validTo + ')'; + $certOption.value = certificate.thumbprint; + + $certificate.appendChild($certOption); }); + }, function (error) { + $certificateError.textContent = '\n' + error.message; + }); })(); diff --git a/examples/script-tag/public/create-sign.js b/examples/script-tag/public/create-sign.js index c5739eb..db33cb2 100644 --- a/examples/script-tag/public/create-sign.js +++ b/examples/script-tag/public/create-sign.js @@ -1,24 +1,52 @@ /** - * Пример создания подписи данных, сгенерированных по ГОСТ Р 34.11-94 - * https://ru.wikipedia.org/wiki/%D0%93%D0%9E%D0%A1%D0%A2_%D0%A0_34.11-94 + * Пример создания подписи данных * */ ;(function () { 'use strict'; - var $errorMsg = document.getElementById('errorMessage'); + var $createSignature = document.forms.createSignature, + $certificate = document.getElementById('certificate'), + $message = document.getElementById('message'), + $hash = document.getElementById('hash'), + $hashError = document.getElementById('hashError'), + $signature = document.getElementById('signature'), + $signatureError = document.getElementById('signatureError'); - document - .getElementById('createSign') - .addEventListener('click', function () { - // Вычислинный hash по ГОСТ Р 34.11-94 для строки: "abc" - var hash = 'b285056dbf18d7392d7677369524dd14747459ed8143997e163b2986f92fd42c', - hashBase64 = window.btoa(hash), - thumbprint = document.getElementById('certList').value; + $createSignature.addEventListener('reset', function () { + window.location.reload(); + }); - window.cryptoPro.createSignature(thumbprint, hashBase64).then(function (signature) { - document.getElementById('createdSign').value = signature; + $createSignature.addEventListener('submit', function (event) { + var thumbprint = $certificate.value, + message = $message.value, + hashingAlgorithm = document.querySelector('input[name="hashingAlgorithm"]:checked').value; + + event.preventDefault(); + + $hash.placeholder = 'Вычисляется...'; + $hash.value = ''; + + window.cryptoPro.createHash(message, hashingAlgorithm).then(function (hash) { + var detachedSignature = document.querySelector('input[name="signatureType"]:checked').value; + + detachedSignature = Boolean(Number(detachedSignature)); + + $hash.value = hash; + + $signature.placeholder = 'Создается...'; + $signature.value = ''; + + window.cryptoPro.createSignature(thumbprint, hash, detachedSignature).then(function (signature) { + $signature.value = signature; }, function (error) { - $errorMsg.textContent = '\n' + error.message; + $signature.placeholder = 'Не создана'; + + $signatureError.textContent = '\n' + error.message; }); + }, function (error) { + $hash.placeholder = 'Не вычислен'; + + $hashError.textContent = '\n' + error.message; }); + }); })(); diff --git a/examples/script-tag/public/get-env-info.js b/examples/script-tag/public/get-env-info.js index becd9f2..be0b864 100644 --- a/examples/script-tag/public/get-env-info.js +++ b/examples/script-tag/public/get-env-info.js @@ -4,17 +4,18 @@ ;(function () { 'use strict'; - var $errorMsg = document.getElementById('errorMessage'); + var $systemInfo = document.getElementById('systemInfo'), + $systemInfoError = document.getElementById('systemInfoError'); window.cryptoPro.getSystemInfo().then(function (systemInfo) { window.cryptoPro.isValidSystemSetup().then(function (isValidSystemSetup) { systemInfo.isValidSystemSetup = isValidSystemSetup; - document.getElementById('systemInfo').textContent = JSON.stringify(systemInfo, null, ' '); - }, function (error) { - $errorMsg.textContent = '\n' + error.message; - }); - }, function (error) { - $errorMsg.textContent = '\n' + error.message; - }); + $systemInfo.textContent = JSON.stringify(systemInfo, null, ' '); + }, handleError); + }, handleError); + + function handleError(error) { + $systemInfoError.textContent = '\n' + error.message; + } })(); diff --git a/examples/script-tag/public/index.html b/examples/script-tag/public/index.html index bc38ebd..24a7bb6 100755 --- a/examples/script-tag/public/index.html +++ b/examples/script-tag/public/index.html @@ -5,14 +5,76 @@ Пример использования cryptoPro - - - -
-

-    
-    

-    

+    
+
+ Создание подписи + +
+ +

+ + +
+ +
+ Информация о сертификате +

+            
+

+
+            
+            
+ +
+ +

+ + +
+
+
+
+
+
+ +

+ +
+ + +
+ +
+ Результат +
+ +
+

+
+            
+ +

+ Для проверки нужно создать файл со сгенерированной подписью в кодировке UTF-8 с расширением *.sig +
+ для совмещенной подписи (или *.sgn для отделенной). +

+ + Проверить подпись + +

+        
+ +
+ Информация о системе +

+            

+        
+
@@ -23,6 +85,7 @@ + diff --git a/examples/script-tag/public/polyfills/details-element-polyfill.js b/examples/script-tag/public/polyfills/details-element-polyfill.js new file mode 100644 index 0000000..473ad23 --- /dev/null +++ b/examples/script-tag/public/polyfills/details-element-polyfill.js @@ -0,0 +1,194 @@ +/* +Details Element Polyfill 2.4.0 +Copyright © 2019 Javan Makhmali + */ +(function() { + "use strict"; + var element = document.createElement("details"); + var elementIsNative = typeof HTMLDetailsElement != "undefined" && element instanceof HTMLDetailsElement; + var support = { + open: "open" in element || elementIsNative, + toggle: "ontoggle" in element + }; + var styles = '\ndetails, summary {\n display: block;\n}\ndetails:not([open]) > *:not(summary) {\n display: none;\n}\nsummary::before {\n content: "►";\n padding-right: 0.3rem;\n font-size: 0.6rem;\n cursor: default;\n}\n[open] > summary::before {\n content: "▼";\n}\n'; + var _ref = [], forEach = _ref.forEach, slice = _ref.slice; + if (!support.open) { + polyfillStyles(); + polyfillProperties(); + polyfillToggle(); + polyfillAccessibility(); + } + if (support.open && !support.toggle) { + polyfillToggleEvent(); + } + function polyfillStyles() { + document.head.insertAdjacentHTML("afterbegin", ""); + } + function polyfillProperties() { + var prototype = document.createElement("details").constructor.prototype; + var setAttribute = prototype.setAttribute, removeAttribute = prototype.removeAttribute; + var open = Object.getOwnPropertyDescriptor(prototype, "open"); + Object.defineProperties(prototype, { + open: { + get: function get() { + if (this.tagName == "DETAILS") { + return this.hasAttribute("open"); + } else { + if (open && open.get) { + return open.get.call(this); + } + } + }, + set: function set(value) { + if (this.tagName == "DETAILS") { + return value ? this.setAttribute("open", "") : this.removeAttribute("open"); + } else { + if (open && open.set) { + return open.set.call(this, value); + } + } + } + }, + setAttribute: { + value: function value(name, _value) { + var _this = this; + var call = function call() { + return setAttribute.call(_this, name, _value); + }; + if (name == "open" && this.tagName == "DETAILS") { + var wasOpen = this.hasAttribute("open"); + var result = call(); + if (!wasOpen) { + var summary = this.querySelector("summary"); + if (summary) summary.setAttribute("aria-expanded", true); + triggerToggle(this); + } + return result; + } + return call(); + } + }, + removeAttribute: { + value: function value(name) { + var _this2 = this; + var call = function call() { + return removeAttribute.call(_this2, name); + }; + if (name == "open" && this.tagName == "DETAILS") { + var wasOpen = this.hasAttribute("open"); + var result = call(); + if (wasOpen) { + var summary = this.querySelector("summary"); + if (summary) summary.setAttribute("aria-expanded", false); + triggerToggle(this); + } + return result; + } + return call(); + } + } + }); + } + function polyfillToggle() { + onTogglingTrigger(function(element) { + element.hasAttribute("open") ? element.removeAttribute("open") : element.setAttribute("open", ""); + }); + } + function polyfillToggleEvent() { + if (window.MutationObserver) { + new MutationObserver(function(mutations) { + forEach.call(mutations, function(mutation) { + var target = mutation.target, attributeName = mutation.attributeName; + if (target.tagName == "DETAILS" && attributeName == "open") { + triggerToggle(target); + } + }); + }).observe(document.documentElement, { + attributes: true, + subtree: true + }); + } else { + onTogglingTrigger(function(element) { + var wasOpen = element.getAttribute("open"); + setTimeout(function() { + var isOpen = element.getAttribute("open"); + if (wasOpen != isOpen) { + triggerToggle(element); + } + }, 1); + }); + } + } + function polyfillAccessibility() { + setAccessibilityAttributes(document); + if (window.MutationObserver) { + new MutationObserver(function(mutations) { + forEach.call(mutations, function(mutation) { + forEach.call(mutation.addedNodes, setAccessibilityAttributes); + }); + }).observe(document.documentElement, { + subtree: true, + childList: true + }); + } else { + document.addEventListener("DOMNodeInserted", function(event) { + setAccessibilityAttributes(event.target); + }); + } + } + function setAccessibilityAttributes(root) { + findElementsWithTagName(root, "SUMMARY").forEach(function(summary) { + var details = findClosestElementWithTagName(summary, "DETAILS"); + summary.setAttribute("aria-expanded", details.hasAttribute("open")); + if (!summary.hasAttribute("tabindex")) summary.setAttribute("tabindex", "0"); + if (!summary.hasAttribute("role")) summary.setAttribute("role", "button"); + }); + } + function eventIsSignificant(event) { + return !(event.defaultPrevented || event.ctrlKey || event.metaKey || event.shiftKey || event.target.isContentEditable); + } + function onTogglingTrigger(callback) { + addEventListener("click", function(event) { + if (eventIsSignificant(event)) { + if (event.which <= 1) { + var element = findClosestElementWithTagName(event.target, "SUMMARY"); + if (element && element.parentNode && element.parentNode.tagName == "DETAILS") { + callback(element.parentNode); + } + } + } + }, false); + addEventListener("keydown", function(event) { + if (eventIsSignificant(event)) { + if (event.keyCode == 13 || event.keyCode == 32) { + var element = findClosestElementWithTagName(event.target, "SUMMARY"); + if (element && element.parentNode && element.parentNode.tagName == "DETAILS") { + callback(element.parentNode); + event.preventDefault(); + } + } + } + }, false); + } + function triggerToggle(element) { + var event = document.createEvent("Event"); + event.initEvent("toggle", false, false); + element.dispatchEvent(event); + } + function findElementsWithTagName(root, tagName) { + return (root.tagName == tagName ? [ root ] : []).concat(typeof root.getElementsByTagName == "function" ? slice.call(root.getElementsByTagName(tagName)) : []); + } + function findClosestElementWithTagName(element, tagName) { + if (typeof element.closest == "function") { + return element.closest(tagName); + } else { + while (element) { + if (element.tagName == tagName) { + return element; + } else { + element = element.parentNode; + } + } + } + } +})(); diff --git a/examples/script-tag/public/show-cert.js b/examples/script-tag/public/show-cert.js index d9ccddd..aadca7f 100644 --- a/examples/script-tag/public/show-cert.js +++ b/examples/script-tag/public/show-cert.js @@ -4,10 +4,18 @@ ;(function () { 'use strict'; - document - .getElementById('showCertificate') - .addEventListener('click', function () { - var thumbprint = document.getElementById('certList').value; + var $certificate = document.getElementById('certificate'), + $certificateDetails = document.getElementById('certificateDetails'), + $certificateInfo = document.getElementById('certificateInfo'), + $certificatesError = document.getElementById('certificatesError'); + + $certificateDetails.style.display = 'none'; + + $certificateDetails.addEventListener('toggle', function () { + if ($certificateDetails.open) { + var thumbprint = $certificate.value; + + $certificateInfo.textContent = 'Запрашивается...'; window.cryptoPro.getCertificate(thumbprint).then(function (certificate) { Promise.all([ @@ -24,7 +32,7 @@ certificate.hasExtendedKeyUsage('1.3.6.1.4.1.311.80.2'), certificate.hasExtendedKeyUsage(['1.3.6.1.5.5.7.3.3', '1.3.6.1.4.1.311.10.3.12']), ]).then(function (results) { - document.getElementById('certificateInfo').textContent = JSON.stringify({ + $certificateInfo.textContent = JSON.stringify({ name: certificate.name, issuerName: certificate.issuerName, subjectName: certificate.subjectName, @@ -44,7 +52,16 @@ '1.3.6.1.4.1.311.80.2': results[10], "'1.3.6.1.5.5.7.3.3', '1.3.6.1.4.1.311.10.3.12'": results[11], }, null, ' '); - }); - }); - }); + }, handleError); + }, handleError); + + return; + } + + $certificateInfo.textContent = ''; + }); + + function handleError(error) { + $certificatesError.textContent = '\n' + error.message; + } })(); diff --git a/src/api/createHash.ts b/src/api/createHash.ts new file mode 100644 index 0000000..edc9f95 --- /dev/null +++ b/src/api/createHash.ts @@ -0,0 +1,71 @@ +import { _afterPluginsLoaded } from '../helpers/_afterPluginsLoaded'; +import { _extractMeaningfulErrorMessage } from '../helpers/_extractMeaningfulErrorMessage'; +import { __cadesAsyncToken__, __createCadesPluginObject__, _generateCadesFn } from '../helpers/_generateCadesFn'; + +type Algorithm = + | 'GOST_3411' + | 'GOST_3411_2012_256' + | 'GOST_3411_2012_512' + | 'GOST_3411_HMAC' + | 'GOST_3411_2012_256_HMAC' + | 'GOST_3411_2012_512_HMAC'; + +/** + * Создает хэш сообщения по ГОСТ Р 34.11 + * https://ru.wikipedia.org/wiki/%D0%A1%D1%82%D1%80%D0%B8%D0%B1%D0%BE%D0%B3_(%D1%85%D0%B5%D1%88-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F) + * + * @param unencryptedMessage - сообщение для хеширования + * @param algorithm - один из алгоритмов хеширования: + * GOST_3411 + * GOST_3411_2012_256 + * GOST_3411_2012_512 - по умолчанию + * GOST_3411_HMAC + * GOST_3411_2012_256_HMAC + * GOST_3411_2012_512_HMAC + * + * @returns хэш + */ +export const createHash = _afterPluginsLoaded( + async (unencryptedMessage: string | ArrayBuffer, algorithm: Algorithm = 'GOST_3411_2012_512'): Promise => { + const { cadesplugin } = window; + + return eval( + _generateCadesFn(function createHash(): string { + const cadesHashedData = __cadesAsyncToken__ + __createCadesPluginObject__('CAdESCOM.HashedData'); + let messageBase64; + let hash; + + try { + messageBase64 = Buffer.from(unencryptedMessage).toString('base64'); + } catch (error) { + console.error(error); + + throw new Error('Ошибка при преобразовании сообщения в Base64'); + } + + try { + void ( + __cadesAsyncToken__ + + cadesHashedData.propset_Algorithm(cadesplugin['CADESCOM_HASH_ALGORITHM_CP_' + algorithm]) + ); + void (__cadesAsyncToken__ + cadesHashedData.propset_DataEncoding(cadesplugin.CADESCOM_BASE64_TO_BINARY)); + void (__cadesAsyncToken__ + cadesHashedData.Hash(messageBase64)); + } catch (error) { + console.error(error); + + throw new Error(_extractMeaningfulErrorMessage(error) || 'Ошибка при инициализации хэширования'); + } + + try { + hash = __cadesAsyncToken__ + cadesHashedData.Value; + } catch (error) { + console.error(error); + + throw new Error(_extractMeaningfulErrorMessage(error) || 'Ошибка при создании хэша'); + } + + return hash; + }), + ); + }, +); diff --git a/src/api/createSignature.ts b/src/api/createSignature.ts index 8b7be8a..7bf0346 100644 --- a/src/api/createSignature.ts +++ b/src/api/createSignature.ts @@ -9,12 +9,12 @@ import { _getDateObj } from '../helpers/_getDateObj'; * Создает подпись base64 строки по отпечатку сертификата * * @param thumbprint - отпечаток сертификата - * @param dataBase64 - строковые данные в формате base64 + * @param messageHash - хэш подписываемого сообщения, сгенерированный по ГОСТ Р 34.11 * @param detachedSignature = true - тип подписи открепленная (true) / присоединенная (false) * @returns подпись */ export const createSignature = _afterPluginsLoaded( - async (thumbprint: string, dataBase64: string, detachedSignature: boolean = true): Promise => { + async (thumbprint: string, messageHash: string, detachedSignature: boolean = true): Promise => { const { cadesplugin } = window; const cadesCertificate = await _getCadesCert(thumbprint); @@ -52,7 +52,7 @@ export const createSignature = _afterPluginsLoaded( cadesAuthAttrs = __cadesAsyncToken__ + cadesSigner.AuthenticatedAttributes2; void (__cadesAsyncToken__ + cadesAuthAttrs.Add(cadesAttrs)); void (__cadesAsyncToken__ + cadesSignedData.propset_ContentEncoding(cadesplugin.CADESCOM_BASE64_TO_BINARY)); - void (__cadesAsyncToken__ + cadesSignedData.propset_Content(dataBase64)); + void (__cadesAsyncToken__ + cadesSignedData.propset_Content(messageHash)); void ( __cadesAsyncToken__ + cadesSigner.propset_Options(cadesplugin.CAPICOM_CERTIFICATE_INCLUDE_END_ENTITY_ONLY) ); diff --git a/src/api/index.ts b/src/api/index.ts index 2fd4070..adc6bcd 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -3,4 +3,5 @@ export * from './getUserCertificates'; export * from './getSystemInfo'; export * from './isValidSystemSetup'; export * from './createSignature'; +export * from './createHash'; export * from './certificate';