Signs and verifies with the eID card, without eParakstitajs 3's 190 MB of
bundled JavaFX. Two binaries, no runtime dependency beyond the card middleware
and libxml2. BSD-licensed; see LICENSE.
Card-signed output passes eParakstitajs 3's own validator end to end: QESIG,
QTSA, profile LT, every check PASSED.
Pick your distribution: install_void.md, install_ubuntu.md,
install_debian.md, or install_other.md. Take note that only void has been
tested. Then:
sh build.sh builds eparaksts.bin and eparaksts_gui.bin
sh check_card_stack.sh card inserted — checks each layer in turn
Build dependencies: a C compiler, OpenSSL headers with the static libssl.a /
libcrypto.a, libxml2 headers, libX11 headers.
| required? | ||
|---|---|---|
| PC/SC daemon + CCID driver | yes | talks to the reader |
| Latvia-eID middleware | yes | eidlv-pkcs11.so, the only way to reach the card |
| eParakstitajs 3 | no | the official app; useful only as a cross-check |
| eParaksts Token Signing | no | browser extension, for signing inside web pages |
| Browser security device | no | card login by TLS client certificate |
The first two are what the install documents cover. The other three live in
optional/ and nothing here looks for them.
The middleware is closed-source and not ours to redistribute, so the Void
installer downloads it from LVRTC and checks its SHA-256; the Debian and Ubuntu
routes install the same .deb natively. It must land at exactly
/opt/latvia-eid — it has RPATH=/opt/latvia-eid/lib and carries its own
OpenSSL 3 and wxWidgets. Do not add that directory to /etc/ld.so.conf.d: its
bundled libcrypto.so.3 would shadow the system one for every process. The
RPATH is enough on its own.
Of the symlinks, the tool needs only /usr/lib/pkcs11/eidlv-pkcs11.so; the
rest are conveniences from the package's own postinst.
./eparaksts.bin info slots, certificates, roles
./eparaksts.bin sign <file> <out.p7s> detached CAdES
./eparaksts.bin verify <file> <sig.p7s> [chain.pem] [--trusted-list]
./eparaksts.bin sign-pdf <in.pdf> <out.pdf> PAdES
./eparaksts.bin verify-pdf <signed.pdf> [chain.pem]
./eparaksts.bin staple <signed.pdf> <out.pdf> add OCSP (B -> LT)
./eparaksts.bin probe <in.pdf> <outdir> variants, one PIN
./eparaksts.bin tsl <ca.pem> EU trusted list lookup
./eparaksts.bin xmlsig <file.xml> check an XMLDSig
./eparaksts.bin selftest format check, no card needed
Flags: --slot N, --pin PIN, --timestamp-url URL, --no-timestamp,
--software-key (throwaway key, for testing file handling without the card).
Slot 0 is the authentication key (PIN1); slot 1 is the qualified signature key
(PIN2) and is the default. Only the second is legally qualified, and verify
says which one was used.
PIN safety: three wrong tries block the key. The tool refuses to start if
the token reports FINAL-TRY, and never retries after a rejected PIN.
./eparaksts_gui.bin
Plain Xlib, core bitmap font, no toolkit, so the window is up immediately.
- Choose file..., or type the path straight into the File path box
- pick PIN1 - rehearsal (slot 0, no legal weight) or PIN2 - qualified (slot 1, a real qualified signature)
- focus the PIN box, type the PIN
- Sign or Verify
Tab moves focus, Enter presses, Esc quits, and each control's accelerator is
the bold letter in its label — P the file path, F choose, 1/2 slot, T
type the PIN, K picker command, S sign, V verify, C copy the log, Q
quit. Ctrl+C and Alt+C also copy the log. Bare letters do nothing while a
text field has focus, so a PIN containing "s" cannot start a signature.
Tab skips the File picker command box — it is set once and then left alone
— but Tab still gets you out of it once you are in. Reach it with K or a
click; its grey background is the reminder that it is not an everyday field.
.pdf in, <name>_signed.pdf out; anything else gets a detached <name>.p7s.
The PIN goes to the tool on stdin, never on its command line where ps would
show it, and is wiped from memory once used.
There is no file dialog. The File path box is editable, so on a machine
with no picker at all you can just type the path. The File picker command
field runs whatever you put in it; %o is the file the picker must write the chosen path into, %d
the directory to start in. It is saved to
~/.config/eparaksts/gui.conf as you type. The default is
st -e vifm --choose-files %o %d
Anything that can write a path to a file will do:
zenity --file-selection > %o
kdialog --getopenfilename %d > %o
alacritty -e ranger --choosefile=%o %d
The apology: the program does not ask XDG, a desktop portal, or anything else what your system's file picker is — it just runs that command. On a machine with neither vifm nor st, Choose file... fails until you edit the field — type the path instead. Doing this properly means a portal round trip over D-Bus, which is more machinery than the rest of this program put together, and it is a very low priority. Sorry.
CAdES-T: detached CMS SignedData, RSA-2048 + SHA-256, signing contentType,
messageDigest and signingCertificateV2, with an RFC 3161 timestamp
attached. In a PDF that blob goes in /Contents with /SubFilter /ETSI.CAdES.detached, which is PAdES; staple then adds OCSP in /DSS,
taking it from PAdES-B to PAdES-LT.
Timestamps come from a public EU-qualified TSA (Izenpe by default) because
eParaksts' own timestamp service sits behind OAuth2 credentials. analysis.md
has the full format and every pitfall met while working it out.
Two slots, and they are not interchangeable:
| slot 0 | slot 1 | |
|---|---|---|
| token | User PIN (Latvia eID) |
Signature PIN (Latvia eID QSCD) |
| keyUsage | digitalSignature, keyEncipherment | nonRepudiation only |
| qcStatements | absent | QcCompliance + QcSSCD |
| meaning | authentication, PIN1 | qualified signature, PIN2 |
A slot 0 signature verifies cryptographically and means nothing legally — the official app rejects it for being unqualified.
- PDFs using cross-reference streams (PDF 1.5+) are refused, not mangled
- so are PDFs that already have an
/AcroForm, which includes already-signed ones — a second signature needs that merge lotl_anchors.pemis trust-on-first-use and expires 2027-11-17- eDoc / ASiC-E is not implemented
- Its own signatures expire after ~15 minutes. It staples an OCSP response
with a 15-minute
nextUpdate, then tests freshness against now rather than validity at signing time, so a PAdES-LT signature it just made stops validating a quarter of an hour later.staplecan refresh such a file. - A signature dictionary with no
/MgivesCannot invoke "Object.getClass()" because "date" is null, reported as a failed signing certificate check — a crash presented as a cryptographic failure. - It asks for both PINs to sign. PIN1 is not needed; this tool signs with PIN2 alone and the result validates.
- It emits BER with indefinite lengths rather than DER.
- Its TSA and OCSP responder sit behind OAuth2, so no third party can use them.
Vibes