@qiwitech/cryptopro

0.1.2 • Public • Published

Web Cryptography and Key Discovery API wrapper for Crypto-Pro CAdES browser plugin

This package exports the following.

  1. High-level Web Cryptography and WebCrypto Key Discovery API with essential methods.
  2. Mid-level wrapper classes for essential Crypto-Pro CAdES API objects.
  3. 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']);

Dependencies (2)

Dev Dependencies (0)

    Package Sidebar

    Install

    npm i @qiwitech/cryptopro

    Weekly Downloads

    0

    Version

    0.1.2

    License

    MIT

    Unpacked Size

    370 kB

    Total Files

    32

    Last publish

    Collaborators

    • qiwitech