@qiwitech/cryptopro
v0.1.2
Published
Web Cryptography and Key Discovery API wrapper for Crypto-Pro CAdES browser plugin
Downloads
10
Maintainers
Readme
Web Cryptography and Key Discovery API wrapper for Crypto-Pro CAdES browser plugin
This package exports the following.
- High-level Web Cryptography and WebCrypto Key Discovery API with essential methods.
- Mid-level wrapper classes for essential Crypto-Pro CAdES API objects.
- Low-level API for communicating with the Crypto-Pro CAdES browser plugin.
To use plugin, you should load its vendor script cadesplugin_api.js which isn't included to this package.
<script type="text/javascript" src="https://www.cryptopro.ru/sites/default/files/products/cades/cadesplugin_api.js"></script>
Differences from W3 specification
Please note that Crypto-Pro client software relies on X.509 certificates rather than PKI cryptography keys. At the same time, Web Cryptography API does not define any methods to manipulate X.509 certificates. So it's impossible to conform the current W3 specification by means of Crypto-Pro CAdES browser plugin.
The NamedCryptoKey
and CryptoKey
instances represent X.509 certificates and reference internal plugin objects.
The SubtleCrypto.generateKey
and SubtleCrypto.exportKey
may be used to generate HMAC keys and PKCS#10 Certificate Signing Requests, while SubtleCrypto.importKey
is able to import X.509 certificates.
The SubtleCrypto.verify
resolves to true
for successfully verified signatures and to false
otherwise. But it may reject in case of any other error, e.g. corrupted CAdES message or any issues with certificate chain.
Web Cryptography API
import { createSubtleCrypto, createCryptoKeys, createHelper, getPlugin } form '@qiwitech/cryptopro';
const plugin = await getPlugin(window),
helper = await createHelper(plugin),
subtle = await createSubtleCrypto(helper),
cryptokeys = await createCryptoKeys(helper);
digest
digest(algo: AlgorithmIdentifier, buffer: ArrayBuffer): Promise<ArrayBuffer>
exportKey
exportKey(format: KeyFormat, key: CryptoKey | NamedCryptoKey): Promise<ArrayBuffer>
generateKey
generateKey(algorithm: AlgorithmIdentifier, extractable: bool, keyUsages: Array<KeyUsage>): Promise<ArrayBuffer>
importKey
importKey(format: KeyFormat, keyData: ArrayBuffer, algorithm: AlgorithmIdentifier, extractable: bool, keyUsages: Array<KeyUsage>): Promise<CryptoKey>
sign
sign(algo: AlgorithmIdentifier, key: CryptoKey | NamedCryptoKey, buffer: ArrayBuffer): Promise<ArrayBuffer>
verify
verify(algo: AlgorithmIdentifier, key: CryptoKey | NamedCryptoKey, signature: ArrayBuffer, buffer: ArrayBuffer): Promise<void>
WebCrypto Key Discovery API
getKeyById
getKeyById(id: string): Promise<?NamedCryptoKey>
getKeyByName
getKeyByName(name: string): Promise<?NamedCryptoKey>
getKeyByExtendedKeyUsage
getKeyByExtendedKeyUsage(oid: string): Promise<?NamedCryptoKey>
getKeysByExtendedKeyUsages
getKeysByExtendedKeyUsages(oids: Array<string>): Promise<Array<NamedCryptoKey>>
API helper
createHelper
createHelper(plugin: Plugin): Promise<Helper>
Note that cadesplugin
is actually an instance of Promise
with injected methods and constants, which resolves to undefined
instead of actual API object. So it's recommended to use getPlugin
helper function.
import { createHelper, getPlugin } form '@qiwitech/cryptopro';
const helper = await createHelper(
await getPlugin(window)
);
It's possible to directly pass a cadesplugin
reference to createHelper
.
import { createHelper } form '@qiwitech/cryptopro';
window.cadesplugin.then(
() => createHelper(window.cadesplugin)
).then(
(helper) => ...
);
createSubtleCrypto
createSubtleCrypto(helper: Helper): Promise<SubtleCrypto>
createCryptoKeys
createCryptoKeys(helper: Helper): Promise<CryptoKeys>
Wrapper classes
Wrapper classes utilize API similar to Node.js Crypto module. It's generally not recommended to use them, but they could be useful for special cases not covered by high-level Web Cryptography API.
There are helper functions available to create instance of any class.
const hash = await helper.createHash(helper.plugin.CADESCOM_HASH_ALGORITHM_CP_GOST_3411_2012_256);
await hash.update('cWl3aXRlY2g=', helper.plugin.CADESCOM_BASE64_TO_BINARY);
const hex = await hash.digest();
It's not recommended, but is possible to use actual classes with the new
keyword. Note they always take Helper and Handle instances as first and second arguments respectively. Some classes have an async populate
method that must be called before any other one.
const hash = new cryptopro.Hash(
helper,
await helper.createHandle('CAdESCOM.HashedData'),
helper.plugin.CADESCOM_HASH_ALGORITHM_CP_GOST_3411_2012_256
);
await hash.populate();
Plugin API
getPlugin
getPlugin(scope: Object, options?: PluginOptions): Promise<Plugin>
The getPlugin
function accepts the scope
(generally, window
) and returns the reference to the Crypto-Pro CAdES browser plugin.
import { getPlugin } form '@qiwitech/cryptopro';
const plugin = await getPlugin(window);
Examples
Get started
import { createSubtleCrypto, createCryptoKeys, createHelper, getPlugin } form '@qiwitech/cryptopro';
const plugin = await getPlugin(window),
helper = await createHelper(plugin),
crypto = {
subtle: await createSubtleCrypto(helper)
},
cryptokeys = await createCryptoKeys(helper);
Sign
const digestAlgo = 'GOST R 34.11-2012-256',
signAlgo = 'GOST R 34.10-2012-256',
text = 'Hello World',
data = new TextEncoder().encode(text).buffer;
const namedKey = await cryptokeys.getKeyByName('...'),
hash = await crypto.subtle.digest(digestAlgo, data);
signature = await crypto.subtle.sign(signAlgo, namedKey, hash),
certificate = await crypto.subtle.exportKey('x509', namedKey),
valid = await crypto.subtle.verify(signAlgo, namedKey, siganture, hash);
await sendToOtherParty(hash, signature, certificate);
// NOTE this also works: both sign() and verify() can pre-hash data for you.
const signature = await crypto.subtle.sign(digestAlgo, namedKey, data),
valid = await crypto.subtle.verify(digestAlgo, namedKey, signature, data);
await sendToOtherParty(data, signature, certificate);
Verify
const digestAlgo = 'GOST R 34.11-2012-256',
signAlgo = 'GOST R 34.10-2012';
const { data, signature, certificate } = await getFromOtherParty(),
publicKey = await crypto.subtle.importKey('x509', certificate, ['verify']),
hash = await crypto.subtle.digest(digestAlgo, data);
valid = await crypto.subtle.verify(signAlgo, publicKey, siganture, hash);
// NOTE this also works: verify() can pre-hash data for you.
const valid = await crypto.subtle.verify(digestAlgo, publicKey, signature, data);
Generate PKCS#10 CSR and install response
The only way to create new keypair is to call SubtleCrypto.generateKey
method to generate a keypair, then use SubtleCrypto.exportKey
to format a PKCS#10 Certificate Signing Request, send it to the trusted Certificate Authority, then receive a newly issued X.509 Certificate and finally import it into available certificate storage using the SubtleCrypto.importKey
method.
const signAlgo = 'GOST R 34.10-2012-256';
const { publicKey, privateKey } = await crypto.subtle.generateKey(signAlgo, false, ['sign', 'verify']);
// NOTE publicKey.name may be used to define certificate Common name (CN).
publicKey.name = 'New certificate';
const csr = await crypto.subtle.exportKey('pkcs10', publicKey),
certificate = await sendToCertificateAutority(csr),
{ publicKey, privateKey } = await crypto.subtle.importKey('x509', certificate, signAlgo, false, ['sign', 'verify']);