Добавил метод вычисления хэша, обновил пример с тэгом script

This commit is contained in:
vgoma 2020-05-05 09:56:07 +03:00
parent 1f452c4ca0
commit e456be7eda
9 changed files with 434 additions and 52 deletions

View File

@ -4,21 +4,28 @@
;(function () { ;(function () {
'use strict'; 'use strict';
var $certs = document.getElementById('certList'), var $certificate = document.getElementById('certificate'),
$errorMsg = document.getElementById('errorMessage'); $createSignature = document.getElementById('createSignature'),
$certificateDetails = document.getElementById('certificateDetails'),
$certificateError = document.getElementById('certificateError');
window.cryptoPro.getUserCertificates() $certificate.addEventListener('change', function handleCertSelection() {
.then(function (certificateList) { var thumbprint = $certificate.value;
certificateList.forEach(function (certificate) {
var $certOption = document.createElement('option');
$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); $certOption.textContent = certificate.name + ' (действителен до: ' + certificate.validTo + ')';
}); $certOption.value = certificate.thumbprint;
}, function (error) {
$errorMsg.textContent = '\n' + error.message; $certificate.appendChild($certOption);
}); });
}, function (error) {
$certificateError.textContent = '\n' + error.message;
});
})(); })();

View File

@ -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 () { ;(function () {
'use strict'; '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 $createSignature.addEventListener('reset', function () {
.getElementById('createSign') window.location.reload();
.addEventListener('click', function () { });
// Вычислинный hash по ГОСТ Р 34.11-94 для строки: "abc"
var hash = 'b285056dbf18d7392d7677369524dd14747459ed8143997e163b2986f92fd42c',
hashBase64 = window.btoa(hash),
thumbprint = document.getElementById('certList').value;
window.cryptoPro.createSignature(thumbprint, hashBase64).then(function (signature) { $createSignature.addEventListener('submit', function (event) {
document.getElementById('createdSign').value = signature; 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) { }, function (error) {
$errorMsg.textContent = '\n' + error.message; $signature.placeholder = 'Не создана';
$signatureError.textContent = '\n' + error.message;
}); });
}, function (error) {
$hash.placeholder = 'Не вычислен';
$hashError.textContent = '\n' + error.message;
}); });
});
})(); })();

View File

@ -4,17 +4,18 @@
;(function () { ;(function () {
'use strict'; 'use strict';
var $errorMsg = document.getElementById('errorMessage'); var $systemInfo = document.getElementById('systemInfo'),
$systemInfoError = document.getElementById('systemInfoError');
window.cryptoPro.getSystemInfo().then(function (systemInfo) { window.cryptoPro.getSystemInfo().then(function (systemInfo) {
window.cryptoPro.isValidSystemSetup().then(function (isValidSystemSetup) { window.cryptoPro.isValidSystemSetup().then(function (isValidSystemSetup) {
systemInfo.isValidSystemSetup = isValidSystemSetup; systemInfo.isValidSystemSetup = isValidSystemSetup;
document.getElementById('systemInfo').textContent = JSON.stringify(systemInfo, null, ' '); $systemInfo.textContent = JSON.stringify(systemInfo, null, ' ');
}, function (error) { }, handleError);
$errorMsg.textContent = '\n' + error.message; }, handleError);
});
}, function (error) { function handleError(error) {
$errorMsg.textContent = '\n' + error.message; $systemInfoError.textContent = '\n' + error.message;
}); }
})(); })();

View File

@ -5,14 +5,76 @@
<title>Пример использования cryptoPro</title> <title>Пример использования cryptoPro</title>
</head> </head>
<body> <body>
<select id="certList"></select> <form name="createSignature" novalidate>
<button id="createSign" type="button">Создать подпись</button> <fieldset>
<button id="showCertificate" type="button">Информация о сертификате</button> <legend>Создание подписи</legend>
<br> <label for="message">Данные для подиси: *</label>
<pre id="certificateInfo"></pre> <br>
<textarea id="createdSign" cols="100" rows="30"></textarea> <textarea id="message" cols="130" rows="5" placeholder="Введите сообщение" autofocus required>abc</textarea>
<pre id="errorMessage"></pre> <br><br>
<pre id="systemInfo"></pre>
<label for="certificate">Сертификат: *</label>
<br>
<select id="certificate" tabindex="-1" required>
<option disabled selected>Не выбран</option>
</select>
<details id="certificateDetails">
<summary>Информация о сертификате</summary>
<pre id="certificateInfo"></pre>
</details>
<pre id="certificateError"></pre>
<label>Тип подписи: *</label>
<br>
<label><input type="radio" name="signatureType" value="0" checked>Совмещенная</label>
<br>
<label><input type="radio" name="signatureType" value="1">Отделенная</label>
<br><br>
<label>Алгоритм хэширования: *</label>
<br>
<label><input type="radio" name="hashingAlgorithm" value="GOST_3411">ГОСТ Р 34.11-94</label><br>
<label><input type="radio" name="hashingAlgorithm" value="GOST_3411_2012_256">ГОСТ Р 34.11-2012 (256)</label><br>
<label><input type="radio" name="hashingAlgorithm" value="GOST_3411_2012_512" checked>ГОСТ Р 34.11-2012 (512)</label><br>
<label><input type="radio" name="hashingAlgorithm" value="GOST_3411_HMAC">ГОСТ Р 34.11-94 (HMAC)</label><br>
<label><input type="radio" name="hashingAlgorithm" value="GOST_3411_2012_256_HMAC">ГОСТ Р 34.11-2012 (HMAC 256)</label><br>
<label><input type="radio" name="hashingAlgorithm" value="GOST_3411_2012_512_HMAC">ГОСТ Р 34.11-2012 (HMAC 512)</label>
<br><br>
<hr>
<button id="createSignature" type="submit" disabled>Создать подпись</button>
<button type="reset">Сбросить</button>
</fieldset>
<fieldset>
<legend>Результат</legend>
<label for="hash">Хэш:</label><br>
<textarea id="hash" cols="130" rows="5" placeholder="Не вычислен"></textarea>
<br>
<pre id="hashError"></pre>
<label for="signature">Подпись:</label><br>
<textarea id="signature" cols="130" rows="30" placeholder="Не создана"></textarea>
<p>
Для проверки нужно создать файл со сгенерированной подписью в кодировке UTF-8 с расширением *.sig
<br>
для совмещенной подписи (или *.sgn для отделенной).
</p>
<a href="https://www.gosuslugi.ru/pgu/eds/"
target="_blank"
rel="nofollow noopener noreferrer"
title="Перейти к проверке подписи">
Проверить подпись
</a>
<pre id="signatureError"></pre>
</fieldset>
<fieldset>
<legend>Информация о системе</legend>
<pre id="systemInfo"></pre>
<pre id="systemInfoError"></pre>
</fieldset>
</form>
<!-- Полифиллы для работы библиотеки --> <!-- Полифиллы для работы библиотеки -->
<script src="polyfills/promise.js"></script> <script src="polyfills/promise.js"></script>
@ -23,6 +85,7 @@
<!-- Полифиллы для работы демо скриптов --> <!-- Полифиллы для работы демо скриптов -->
<script src="polyfills/atob-btoa.js"></script> <script src="polyfills/atob-btoa.js"></script>
<script src="polyfills/details-element-polyfill.js"></script>
<!-- Демо скрипты --> <!-- Демо скрипты -->
<script src="cert-list.js"></script> <script src="cert-list.js"></script>

View File

@ -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", "<style>" + styles + "</style>");
}
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;
}
}
}
}
})();

View File

@ -4,10 +4,18 @@
;(function () { ;(function () {
'use strict'; 'use strict';
document var $certificate = document.getElementById('certificate'),
.getElementById('showCertificate') $certificateDetails = document.getElementById('certificateDetails'),
.addEventListener('click', function () { $certificateInfo = document.getElementById('certificateInfo'),
var thumbprint = document.getElementById('certList').value; $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) { window.cryptoPro.getCertificate(thumbprint).then(function (certificate) {
Promise.all([ Promise.all([
@ -24,7 +32,7 @@
certificate.hasExtendedKeyUsage('1.3.6.1.4.1.311.80.2'), 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']), certificate.hasExtendedKeyUsage(['1.3.6.1.5.5.7.3.3', '1.3.6.1.4.1.311.10.3.12']),
]).then(function (results) { ]).then(function (results) {
document.getElementById('certificateInfo').textContent = JSON.stringify({ $certificateInfo.textContent = JSON.stringify({
name: certificate.name, name: certificate.name,
issuerName: certificate.issuerName, issuerName: certificate.issuerName,
subjectName: certificate.subjectName, subjectName: certificate.subjectName,
@ -44,7 +52,16 @@
'1.3.6.1.4.1.311.80.2': results[10], '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], "'1.3.6.1.5.5.7.3.3', '1.3.6.1.4.1.311.10.3.12'": results[11],
}, null, ' '); }, null, ' ');
}); }, handleError);
}); }, handleError);
});
return;
}
$certificateInfo.textContent = '';
});
function handleError(error) {
$certificatesError.textContent = '\n' + error.message;
}
})(); })();

71
src/api/createHash.ts Normal file
View File

@ -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<string> => {
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;
}),
);
},
);

View File

@ -9,12 +9,12 @@ import { _getDateObj } from '../helpers/_getDateObj';
* Создает подпись base64 строки по отпечатку сертификата * Создает подпись base64 строки по отпечатку сертификата
* *
* @param thumbprint - отпечаток сертификата * @param thumbprint - отпечаток сертификата
* @param dataBase64 - строковые данные в формате base64 * @param messageHash - хэш подписываемого сообщения, сгенерированный по ГОСТ Р 34.11
* @param detachedSignature = true - тип подписи открепленная (true) / присоединенная (false) * @param detachedSignature = true - тип подписи открепленная (true) / присоединенная (false)
* @returns подпись * @returns подпись
*/ */
export const createSignature = _afterPluginsLoaded( export const createSignature = _afterPluginsLoaded(
async (thumbprint: string, dataBase64: string, detachedSignature: boolean = true): Promise<string> => { async (thumbprint: string, messageHash: string, detachedSignature: boolean = true): Promise<string> => {
const { cadesplugin } = window; const { cadesplugin } = window;
const cadesCertificate = await _getCadesCert(thumbprint); const cadesCertificate = await _getCadesCert(thumbprint);
@ -52,7 +52,7 @@ export const createSignature = _afterPluginsLoaded(
cadesAuthAttrs = __cadesAsyncToken__ + cadesSigner.AuthenticatedAttributes2; cadesAuthAttrs = __cadesAsyncToken__ + cadesSigner.AuthenticatedAttributes2;
void (__cadesAsyncToken__ + cadesAuthAttrs.Add(cadesAttrs)); void (__cadesAsyncToken__ + cadesAuthAttrs.Add(cadesAttrs));
void (__cadesAsyncToken__ + cadesSignedData.propset_ContentEncoding(cadesplugin.CADESCOM_BASE64_TO_BINARY)); void (__cadesAsyncToken__ + cadesSignedData.propset_ContentEncoding(cadesplugin.CADESCOM_BASE64_TO_BINARY));
void (__cadesAsyncToken__ + cadesSignedData.propset_Content(dataBase64)); void (__cadesAsyncToken__ + cadesSignedData.propset_Content(messageHash));
void ( void (
__cadesAsyncToken__ + cadesSigner.propset_Options(cadesplugin.CAPICOM_CERTIFICATE_INCLUDE_END_ENTITY_ONLY) __cadesAsyncToken__ + cadesSigner.propset_Options(cadesplugin.CAPICOM_CERTIFICATE_INCLUDE_END_ENTITY_ONLY)
); );

View File

@ -3,4 +3,5 @@ export * from './getUserCertificates';
export * from './getSystemInfo'; export * from './getSystemInfo';
export * from './isValidSystemSetup'; export * from './isValidSystemSetup';
export * from './createSignature'; export * from './createSignature';
export * from './createHash';
export * from './certificate'; export * from './certificate';