Актуализировал пример использования с React

This commit is contained in:
vgoma 2020-10-07 18:54:57 +03:00
parent 3d71049078
commit 864c05f81c
14 changed files with 347 additions and 211 deletions

View File

@ -1,38 +0,0 @@
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

View File

@ -1,21 +1,118 @@
import React from 'react'; import React, { useState } from 'react';
import './App.css'; import { createAttachedSignature, createDetachedSignature, createHash } from 'crypto-pro';
import CryptoPro from './components/CryptoPro'; import Message from './components/Message';
import Certificate from './components/Certificate';
import SignatureType from './components/SignatureType';
import Hash from './components/Hash';
import Signature from './components/Signature';
import SystemInfo from './components/SystemInfo';
function App() { function App() {
const [message, setMessage] = useState('');
const [certificate, setCertificate] = useState(null);
const [detachedSignature, setSignatureType] = useState(null);
const [hash, setHash] = useState('');
const [hashStatus, setHashStatus] = useState('Не вычислен');
const [hashError, setHashError] = useState(null);
const [signature, setSignature] = useState('');
const [signatureStatus, setSignatureStatus] = useState('Не создана');
const [signatureError, setSignatureError] = useState(null);
async function createSignature(event) {
let hash;
event.preventDefault();
setSignature('');
setSignatureError(null);
setHash('');
setHashError(null);
setHashStatus('Вычисляется...');
try {
hash = await createHash(message);
setHash(hash);
} catch (error) {
setHashError(error.message);
return;
}
setHashStatus('Не вычислен');
setSignatureStatus('Создается...');
if (detachedSignature) {
try {
setSignature(await createDetachedSignature(certificate.thumbprint, hash));
} catch (error) {
setSignatureError(error.message);
}
setSignatureStatus('Не создана');
return;
}
try {
setSignature(await createAttachedSignature(certificate.thumbprint, message));
} catch (error) {
setSignatureError(error.message);
}
setSignatureStatus('Не создана');
}
return ( return (
<CryptoPro>{({certificate, setCertificate, signature, setSignature, error, setError}) => <>
<> <form onSubmit={createSignature} noValidate>
<CryptoPro.CertList onChange={setCertificate} onError={setError}/> <fieldset>
<CryptoPro.Sign certificate={certificate} onSign={setSignature} onError={setError}> <Message onChange={setMessage}/>
Создать подпись
</CryptoPro.Sign> <br/><br/>
<CryptoPro.CertInfo certificate={certificate} onError={setError}/>
<textarea value={signature} readOnly cols="100" rows="30"/> <Certificate onChange={setCertificate}/>
<pre>{error}</pre>
<CryptoPro.SystemInfo onError={setError}/> <SignatureType onChange={setSignatureType}/>
</>
}</CryptoPro> <br/><br/>
<hr/>
<button
type="submit"
disabled={!certificate || !message}>
Создать подпись
</button>
</fieldset>
</form>
<fieldset>
<Hash
hash={hash}
hashStatus={hashStatus}
hashError={hashError}/>
<Signature
signature={signature}
signatureStatus={signatureStatus}
signatureError={signatureError}/>
<p>
Для <a href="https://www.gosuslugi.ru/pgu/eds/"
target="_blank"
rel="nofollow noopener noreferrer"
title="Перейти к проверке подписи">проверки</a> нужно
создать файл со сгенерированной подписью в кодировке UTF-8 с расширением *.sgn
<br/>
для отделенной подписи (или *.sig для совмещенной).
</p>
</fieldset>
<fieldset>
<SystemInfo/>
</fieldset>
</>
); );
} }

View File

@ -0,0 +1,101 @@
import React, { useState, useEffect } from 'react';
import { getCertificate, getUserCertificates } from 'crypto-pro';
function Certificate({onChange}) {
const [certificates, setCertificates] = useState([]);
const [certificatesError, setCertificatesError] = useState([]);
const [certificate, setCertificate] = useState(null);
const [certificateDetails, setCertificateDetails] = useState(null);
const [detailsError, setDetailsError] = useState(null);
function selectCertificate(event) {
const certificate = certificates.find(({thumbprint}) => thumbprint === event.target.value);
setCertificate(certificate);
onChange(certificate);
}
async function loadCertificateDetails(thumbprint) {
try {
const certificate = await getCertificate(thumbprint);
setCertificateDetails({
name: certificate.name,
issuerName: certificate.issuerName,
subjectName: certificate.subjectName,
thumbprint: certificate.thumbprint,
validFrom: certificate.validFrom,
validTo: certificate.validTo,
isValid: await certificate.isValid(),
version: await certificate.getCadesProp('Version'),
base64: await certificate.exportBase64(),
algorithm: await certificate.getAlgorithm(),
extendedKeyUsage: await certificate.getExtendedKeyUsage(),
ownerInfo: await certificate.getOwnerInfo(),
issuerInfo: await certificate.getIssuerInfo(),
decodedExtendedKeyUsage: await certificate.getDecodedExtendedKeyUsage(),
'1.3.6.1.4.1.311.80.1': await certificate.hasExtendedKeyUsage('1.3.6.1.4.1.311.80.1'),
'[\'1.3.6.1.5.5.7.3.2\', \'1.3.6.1.4.1.311.10.3.12\']': await certificate.hasExtendedKeyUsage([
'1.3.6.1.5.5.7.3.2',
'1.3.6.1.4.1.311.10.3.12'
]),
'1.3.6.1.4.1.311.80.2': await certificate.hasExtendedKeyUsage('1.3.6.1.4.1.311.80.2'),
'\'1.3.6.1.5.5.7.3.3\', \'1.3.6.1.4.1.311.10.3.12\'': await certificate.hasExtendedKeyUsage([
'1.3.6.1.5.5.7.3.3',
'1.3.6.1.4.1.311.10.3.12'
]),
});
} catch (error) {
setDetailsError(error);
}
}
useEffect(() => {
(async () => {
try {
setCertificates(await getUserCertificates());
} catch (error) {
setCertificatesError(error.message);
}
})();
});
return (
<>
<label htmlFor="certificate">Сертификат: *</label>
<br/>
<select id="certificate" onChange={selectCertificate}>
<option defaultValue={null}>Не выбран</option>
{certificates.map(({name, thumbprint, validTo}) =>
<option key={thumbprint} value={thumbprint}>
{name + ' (действителен до: ' + validTo + ')'}
</option>
)}
</select>
<pre>{certificatesError || null}</pre>
{certificate ? (
<>
<details
onClick={loadCertificateDetails.bind(this, certificate.thumbprint)}>
<summary>Информация о сертификате</summary>
<pre>
{certificateDetails ? (
JSON.stringify(certificateDetails, null, ' ')
) : 'Запрашивается...'}
</pre>
</details>
<pre>{detailsError || null}</pre>
</>
) : null}
</>
);
}
export default Certificate;

View File

@ -1,55 +0,0 @@
import React, { useState } from 'react';
function CertInfo({certificate, onError}) {
const [certInfo, setCertInfo] = useState(null);
async function showCertInfo() {
try {
setCertInfo({
name: certificate.name,
issuerName: certificate.issuerName,
subjectName: certificate.subjectName,
thumbprint: certificate.thumbprint,
validFrom: certificate.validFrom,
validTo: certificate.validTo,
isValid: await certificate.isValid(),
version: await certificate.getCadesProp('Version'),
base64: await certificate.exportBase64(),
algorithm: await certificate.getAlgorithm(),
extendedKeyUsage: await certificate.getExtendedKeyUsage(),
ownerInfo: await certificate.getOwnerInfo(),
issuerInfo: await certificate.getIssuerInfo(),
decodedExtendedKeyUsage: await certificate.getDecodedExtendedKeyUsage(),
'1.3.6.1.4.1.311.80.1': await certificate.hasExtendedKeyUsage('1.3.6.1.4.1.311.80.1'),
"['1.3.6.1.5.5.7.3.2', '1.3.6.1.4.1.311.10.3.12']": await certificate.hasExtendedKeyUsage([
'1.3.6.1.5.5.7.3.2',
'1.3.6.1.4.1.311.10.3.12'
]),
'1.3.6.1.4.1.311.80.2': await certificate.hasExtendedKeyUsage('1.3.6.1.4.1.311.80.2'),
"'1.3.6.1.5.5.7.3.3', '1.3.6.1.4.1.311.10.3.12'": await certificate.hasExtendedKeyUsage([
'1.3.6.1.5.5.7.3.3',
'1.3.6.1.4.1.311.10.3.12'
]),
});
} catch (error) {
onError(error.message);
}
}
return (
<>
<button
type="button"
onClick={showCertInfo}
disabled={!certificate}>
Информация о сертификате
</button>
<br/>
{certInfo ? (
<pre>{JSON.stringify(certInfo, null, ' ')}</pre>
) : null}
</>
);
}
export default CertInfo;

View File

@ -1,31 +0,0 @@
import React, { useState, useEffect } from 'react';
import { getUserCertificates } from 'crypto-pro';
function CertList({onChange, onError}) {
const [certificates, setCertificates] = useState([]);
function selectCertificate(event) {
onChange(certificates.find(({thumbprint}) => thumbprint === event.target.value));
}
useEffect(() => {
(async () => {
try {
setCertificates(await getUserCertificates());
} catch (error) {
onError(error.message);
}
})();
}, [onError]);
return (
<select onChange={selectCertificate}>
<option defaultValue={null}>Не выбран</option>
{certificates.map(({name, thumbprint}) =>
<option key={thumbprint} value={thumbprint}>{name}</option>
)}
</select>
);
}
export default CertList;

View File

@ -1,27 +0,0 @@
import React from 'react';
import { createSignature } from 'crypto-pro';
function Sign({certificate, onSign, onError, children}) {
async function sign() {
// Вычислинный hash по ГОСТ Р 34.11-94 для строки: "abc"
const hash = 'b285056dbf18d7392d7677369524dd14747459ed8143997e163b2986f92fd42c';
const hashBase64 = window.btoa(hash);
try {
onSign(await createSignature(certificate.thumbprint, hashBase64));
} catch (error) {
onError(error.message);
}
}
return (
<button
type="button"
disabled={!certificate}
onClick={sign}>
{children}
</button>
);
}
export default Sign;

View File

@ -1,27 +0,0 @@
import { useState } from 'react';
import CertList from './CertList';
import CertInfo from './CertInfo';
import Sign from './Sign';
import SystemInfo from './SystemInfo';
const CryptoPro = ({children}) => {
const [certificate, setCertificate] = useState(null);
const [signature, setSignature] = useState('');
const [error, setError] = useState('');
return children({
certificate,
setCertificate,
signature,
setSignature,
error,
setError
});
};
CryptoPro.CertList = CertList;
CryptoPro.Sign = Sign;
CryptoPro.CertInfo = CertInfo;
CryptoPro.SystemInfo = SystemInfo;
export default CryptoPro;

View File

@ -0,0 +1,25 @@
import React from 'react';
function Hash({hash, hashStatus, hashError}) {
return (
<>
<legend>Результат</legend>
<label htmlFor="hash">Хэш (ГОСТ Р 34.11-2012 256 бит):</label>
<br/>
<textarea
id="hash"
cols="80"
rows="5"
value={hash}
placeholder={hashStatus}
readOnly/>
<pre>{hashError || null}</pre>
</>
)
}
export default Hash;

View File

@ -0,0 +1,35 @@
import React, { useEffect, useState } from 'react';
function Message({onChange}) {
const [message, setMessage] = useState('Привет мир!');
function onMessageChange(event) {
setMessage(event.target.value);
onChange(event.target.value);
}
useEffect(() => onChange(message));
return (
<>
<legend>Создание подписи</legend>
<label htmlFor="message">Подписываемое сообщение: *</label>
<br/>
<textarea
id="message"
name="message"
cols="80"
rows="5"
placeholder="Введите сообщение"
value={message}
onChange={onMessageChange}
autoFocus
required/>
</>
);
}
export default Message;

View File

@ -0,0 +1,23 @@
import React from 'react';
function Signature({signature, signatureStatus, signatureError}) {
return (
<>
<label htmlFor="signature">Подпись (PKCS7):</label>
<br/>
<textarea
id="signature"
cols="80"
rows="30"
value={signature}
placeholder={signatureStatus}
readOnly/>
<pre>{signatureError || null}</pre>
</>
)
}
export default Signature;

View File

@ -0,0 +1,36 @@
import React, { useEffect, useState } from 'react';
function SignatureType({onChange}) {
const [type, setType] = useState(true);
function onTypeToggle() {
setType(!type);
onChange(!type);
}
useEffect(() => onChange(type));
return (
<>
<label>Тип подписи: *</label>
<br/>
<label>
<input
type="radio"
checked={!type}
onChange={onTypeToggle}/>Совмещенная</label>
<br/>
<label>
<input
type="radio"
checked={type}
onChange={onTypeToggle}/>Отделенная</label>
</>
)
}
export default SignatureType;

View File

@ -1,8 +1,9 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { getSystemInfo, isValidSystemSetup } from 'crypto-pro'; import { getSystemInfo, isValidSystemSetup } from 'crypto-pro';
function SystemInfo({onError}) { function SystemInfo() {
const [systemInfo, setSystemInfo] = useState(null); const [systemInfo, setSystemInfo] = useState(null);
const [systemInfoError, setSystemInfoError] = useState(null);
useEffect(() => { useEffect(() => {
(async () => { (async () => {
@ -12,17 +13,27 @@ function SystemInfo({onError}) {
isValidSystemSetup: await isValidSystemSetup() isValidSystemSetup: await isValidSystemSetup()
}); });
} catch (error) { } catch (error) {
onError(error.message); setSystemInfoError(error.message);
} }
})(); })();
}, [onError]); });
if (!systemInfo) { if (!systemInfo) {
return null; return null;
} }
return ( return (
<pre>{JSON.stringify(systemInfo, null, ' ')}</pre> <>
<legend>Информация о системе</legend>
<pre>
{systemInfo ? (
JSON.stringify(systemInfo, null, ' ')
) : (
systemInfoError || null
)}
</pre>
</>
); );
} }

View File

@ -1,13 +0,0 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

View File

@ -3,7 +3,6 @@ import 'react-app-polyfill/stable';
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import './index.css';
import App from './App'; import App from './App';
import * as serviceWorker from './serviceWorker'; import * as serviceWorker from './serviceWorker';