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—Byteswith 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 isCAdES-T, butXAdES-B-TandPAdES-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.formatworks 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" |