Documentation · Widgets

Signing documents

The sign widget applies a qualified electronic signature (QES) to any data. The user picks a key file and enters the password inside the iframe — they never reach your page; only the finished signature comes back.

Minimal example

import { embed } from 'https://dstucrypt.io/embed/dstucrypt-embed.mjs';

const signer = await embed('sign', { mount: 'modal' });

const { signature } = await signer.sign(fileBytes, { format: 'CAdES-T', digest: 'gost-34311' });

signature.download('document.p7s');       //  hand the file to the user
//  or send it to your backend:
await fetch('/api/save', { method: 'POST', body: signature.bytes });

signer.destroy();

API

const { signature, signerCertId, accredited } = await signer.sign(data, options);
  • data — File | Uint8Array | ArrayBuffer | string.
  • signature — Bytes with the finished signature.
  • signerCertId — identifier of the signer's certificate.
  • accredited — whether this is a QES: true — the certificate is from an accredited CA; false — it is not (a signature without QES status); null — accreditation was not checked. This field is also surfaced to the user with a warning inside the widget itself.

options parameters

Parameter Default Description
format 'CAdES-BES' signature format and level (see the table below)
detached false detached signature (the data is not embedded into the container)
fileName 'document' file name inside the ASiC container
digest 'gost-34311' signature digest: 'gost-34311' (DSTU GOST 34.311-95, the default — this is what government validators accept) or 'kupyna-256' (DSTU 7564:2014, Kupyna). The matching DSTU 4145 signature algorithm is selected automatically along with the digest. For RSA/ECDSA keys the core picks the digest itself
includeContentTS false timestamp over the data itself (contentTS) — for CAdES-T and above
checkStatus auto for -C/-XL embed the certificate's OCSP status into the container. For CAdES-C and CAdES-XL it is enabled automatically (without revocation data these levels are incomplete); for other levels it can be enabled manually. Requires the CA's online OCSP service

Signature formats

16 formats are supported — four families, each with its own levels. The higher the level, the more data for long-term and offline verification is embedded into the signature.

CAdES — a CMS signature, .p7s file (any data):

format Level What it adds
CAdES-BES base signing without network access
CAdES-T + timestamp TSP over the signature
CAdES-C + references references to the certificates and OCSP/CRL used for verification
CAdES-XL + full data the certificates and OCSP/CRL themselves (long-term verification)

PAdES — signature embedded into the PDF, .pdf file:

format Level
PAdES-B-B base
PAdES-B-T + timestamp
PAdES-B-LT + data for long-term verification
PAdES-B-LTA + archival timestamp

XAdES — signature in XML, .xml file:

format Level
XAdES-BES base
XAdES-B-T + timestamp
XAdES-B-LT + data for long-term verification
XAdES-B-LTA + archival timestamp

ASiC — a ZIP container (original + signature):

format File Level
ASiC-S-BES .asics simple container, base
ASiC-S-T .asics simple container, + timestamp
ASiC-E-BES .asice extended container, base
ASiC-E-T .asice extended container, + timestamp

About level names. CAdES uses the older nomenclature (-BES/-T/-C/-XL), while XAdES and PAdES use the newer ETSI baseline scheme (-BES/-B-T/-B-LT/-B-LTA). So the "with timestamp" level is CAdES-T, but XAdES-B-T and PAdES-B-T.

The default digest is DSTU GOST 34.311-95 (digest:'gost-34311'): the "DSTU 4145 signature + GOST 34.311 digest" pair is what government validators (Diia, the central CA (CZO)) accept today. Enable the modern Kupyna (DSTU 7564:2014) explicitly — digest:'kupyna-256' — for keys that support it. Once government systems move to Kupyna, you can make it the default with a single line, no rebuild required.

Timestamps — nothing to configure

For CAdES-T and above, timestamps are issued by our built-in TSP servers with automatic fallbacks (if one is unavailable, the request is retried against the next one). The CORS proxy is ours too. No tspUrl/proxyUrl in your code.

The base subscription includes 1,000 timestamps per day per domain. Beyond the limit the widget returns a TSP_QUOTA error ("Daily timestamp limit exhausted").

Key in an app: Diia.Signature and Smart ID

Besides a file key, the widget can sign with a key that lives in the user's app — Diia or Privat24. You define the set of methods:

const signer = await embed('sign', { providers: ['file', 'diia', 'smartid'] });
const { signature, provider } = await signer.sign(file, { fileName: file.name });
//  provider — what the user ultimately signed with: 'file' | 'diia' | 'smartid'

If there are several methods, the user chooses in the signing window; if there is one, there is no switcher at all. The document never leaves your page: only the digest is sent to the provider. Details, limitations and the differences between the two providers are on the Diia.Signature and Smart ID page.

In short, here is what changes in your code:

  • No need to set digest — each provider accepts its own algorithm, and the widget substitutes the right one itself (Diia — GOST 34.311, Smart ID — Kupyna-256).
  • signBatch() saves the user extra steps: several files are signed in a single session — one QR code and one confirmation in the app.
  • format works as usual, including the long-term levels.
  • So does detached: the provider physically returns a detached signature, and by default the widget embeds your data into it — the signature value does not change.

What the user sees

A single "Sign" button: pick the key file (PKCS#12/PFX, JKS, PKCS#8, Key-6.dat), enter the password — the widget opens the container itself, selects the signing key (by keyUsage) and signs. If the container has no certificate, a field for a separate certificate file appears.

Errors are shown in plain language: "Wrong key password", "Timestamp server unavailable", and so on.

Signing without the password every time

To spare the user from picking the key and entering the password on every signature, enable automatic mode: the key is stored inside the widget (separately for your domain, encrypted with a PIN code), and sign() runs without the modal.

Multi-signature — several signers

The "Multiple signers" option lets you add another signer to an already signed document (CMS) — the coSign() method. This is a paid option enabled separately per domain (see pricing; available in the trial for evaluation).

const signer = await embed('sign', { mount: 'modal' });

//  existingSignature — the finished .p7s (from the first signer),
//  data — the same data that was signed.
const { signature } = await signer.coSign(existingSignature, data);

signature.download('document.p7s');   //  the CMS now carries two signatures
  • The usual key-selection modal opens — the second user signs.
  • Returns the same CMS with an added SignerInfo (parallel signature).
  • If the option has not been paid for on the domain, the method throws an error with error.code === 'MULTISIGN_NOT_ENABLED'.

Common errors in integrator code

error.code / message Cause
LICENSE_INACTIVE the domain is not activated (the trial/subscription has ended)
MULTISIGN_NOT_ENABLED the "Multiple signers" option is not enabled for the domain
TSP_QUOTA the daily timestamp limit has been exhausted
CANCELLED the user clicked "Cancel"