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
-
-
-
-
-
-
-
-
+
@@ -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';