npm package: @qiwitech/cryptopro
Description: Web Cryptography and Key Discovery API wrapper for Crypto-Pro CAdES browser plugin
Author: Ivan Khrulev
Latest version: 0.1.2

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

This package exports the following.

1. High-level [Web Cryptography](https://www.w3.org/TR/WebCryptoAPI/) and [WebCrypto Key Discovery](https://www.w3.org/TR/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](https://www.cryptopro.ru/products/cades/plugin/).

To use plugin, you should load its vendor script [cadesplugin_api.js](https://www.cryptopro.ru/sites/default/files/products/cades/cadesplugin_api.js) which isn't included to this package.

```html
<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

```typescript
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`

```typescript
digest(algo: AlgorithmIdentifier, buffer: ArrayBuffer): Promise<ArrayBuffer>
```

### `exportKey`

```typescript
exportKey(format: KeyFormat, key: CryptoKey | NamedCryptoKey): Promise<ArrayBuffer>
```

### `generateKey`

```typescript
generateKey(algorithm: AlgorithmIdentifier, extractable: bool, keyUsages: Array<KeyUsage>): Promise<ArrayBuffer>
```

### `importKey`

```typescript
importKey(format: KeyFormat, keyData: ArrayBuffer, algorithm: AlgorithmIdentifier, extractable: bool, keyUsages: Array<KeyUsage>): Promise<CryptoKey>
```

### `sign`

```typescript
sign(algo: AlgorithmIdentifier, key: CryptoKey | NamedCryptoKey, buffer: ArrayBuffer): Promise<ArrayBuffer>
```

### `verify`

```typescript
verify(algo: AlgorithmIdentifier, key: CryptoKey | NamedCryptoKey, signature: ArrayBuffer, buffer: ArrayBuffer): Promise<void>
```

## WebCrypto Key Discovery API

### `getKeyById`

```typescript
getKeyById(id: string): Promise<?NamedCryptoKey>
```

### `getKeyByName`

```typescript
getKeyByName(name: string): Promise<?NamedCryptoKey>
```

### `getKeyByExtendedKeyUsage`

```typescript
getKeyByExtendedKeyUsage(oid: string): Promise<?NamedCryptoKey>
```

### `getKeysByExtendedKeyUsages`

```typescript
getKeysByExtendedKeyUsages(oids: Array<string>): Promise<Array<NamedCryptoKey>>
```

## API helper

### `createHelper`

```typescript
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.

```typescript
import { createHelper, getPlugin } form '@qiwitech/cryptopro';

const helper = await createHelper(
	await getPlugin(window)
);
```

It's possible to directly pass a `cadesplugin` reference to `createHelper`.

```typescript
import { createHelper } form '@qiwitech/cryptopro';

window.cadesplugin.then(
	() => createHelper(window.cadesplugin)
).then(
	(helper) => ...
);
```

### `createSubtleCrypto`

```typescript
createSubtleCrypto(helper: Helper): Promise<SubtleCrypto>
```

### `createCryptoKeys`

```typescript
createCryptoKeys(helper: Helper): Promise<CryptoKeys>
```

### Wrapper classes

Wrapper classes utilize API similar to [Node.js Crypto](https://nodejs.org/dist/latest/docs/api/crypto.html) 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.

```typescript
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.

```typescript
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`

```typescript
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.

```typescript
import { getPlugin } form '@qiwitech/cryptopro';

const plugin = await getPlugin(window);
```

## Examples

### Get started

```typescript
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

```typescript
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

```typescript
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.

```typescript
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']);
```