mirror of
https://github.com/crypto-pro-web/crypto-pro-js.git
synced 2024-11-24 00:55:00 +03:00
Актуализировал пример использования с React
This commit is contained in:
parent
3d71049078
commit
864c05f81c
@ -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);
|
||||
}
|
||||
}
|
@ -1,21 +1,118 @@
|
||||
import React from 'react';
|
||||
import './App.css';
|
||||
import CryptoPro from './components/CryptoPro';
|
||||
import React, { useState } from 'react';
|
||||
import { createAttachedSignature, createDetachedSignature, createHash } from 'crypto-pro';
|
||||
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() {
|
||||
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 (
|
||||
<CryptoPro>{({certificate, setCertificate, signature, setSignature, error, setError}) =>
|
||||
<>
|
||||
<CryptoPro.CertList onChange={setCertificate} onError={setError}/>
|
||||
<CryptoPro.Sign certificate={certificate} onSign={setSignature} onError={setError}>
|
||||
Создать подпись
|
||||
</CryptoPro.Sign>
|
||||
<CryptoPro.CertInfo certificate={certificate} onError={setError}/>
|
||||
<textarea value={signature} readOnly cols="100" rows="30"/>
|
||||
<pre>{error}</pre>
|
||||
<CryptoPro.SystemInfo onError={setError}/>
|
||||
</>
|
||||
}</CryptoPro>
|
||||
<>
|
||||
<form onSubmit={createSignature} noValidate>
|
||||
<fieldset>
|
||||
<Message onChange={setMessage}/>
|
||||
|
||||
<br/><br/>
|
||||
|
||||
<Certificate onChange={setCertificate}/>
|
||||
|
||||
<SignatureType onChange={setSignatureType}/>
|
||||
|
||||
<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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
101
examples/react/src/components/Certificate.js
Normal file
101
examples/react/src/components/Certificate.js
Normal 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;
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
25
examples/react/src/components/Hash.js
Normal file
25
examples/react/src/components/Hash.js
Normal 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;
|
35
examples/react/src/components/Message.js
Normal file
35
examples/react/src/components/Message.js
Normal 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;
|
23
examples/react/src/components/Signature.js
Normal file
23
examples/react/src/components/Signature.js
Normal 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;
|
36
examples/react/src/components/SignatureType.js
Normal file
36
examples/react/src/components/SignatureType.js
Normal 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;
|
@ -1,8 +1,9 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { getSystemInfo, isValidSystemSetup } from 'crypto-pro';
|
||||
|
||||
function SystemInfo({onError}) {
|
||||
function SystemInfo() {
|
||||
const [systemInfo, setSystemInfo] = useState(null);
|
||||
const [systemInfoError, setSystemInfoError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
@ -12,17 +13,27 @@ function SystemInfo({onError}) {
|
||||
isValidSystemSetup: await isValidSystemSetup()
|
||||
});
|
||||
} catch (error) {
|
||||
onError(error.message);
|
||||
setSystemInfoError(error.message);
|
||||
}
|
||||
})();
|
||||
}, [onError]);
|
||||
});
|
||||
|
||||
if (!systemInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<pre>{JSON.stringify(systemInfo, null, ' ')}</pre>
|
||||
<>
|
||||
<legend>Информация о системе</legend>
|
||||
|
||||
<pre>
|
||||
{systemInfo ? (
|
||||
JSON.stringify(systemInfo, null, ' ')
|
||||
) : (
|
||||
systemInfoError || null
|
||||
)}
|
||||
</pre>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -3,7 +3,6 @@ import 'react-app-polyfill/stable';
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import * as serviceWorker from './serviceWorker';
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user