
Client-side PKI toolbox that decodes X.509, CSR, chain, CRL, PKCS#7 and PKCS#12 artifacts, views ASN.1, converts formats, and generates self-signed certificates in the browser.
A self-hosted, 100 % client-side toolbox for PKI artefacts: X.509 certificates, PKCS#10 CSRs, chains, CRLs, PKCS#7 and PKCS#12 bundles, raw ASN.1, format conversion, fingerprints and certificate issuance. A privacy-respecting, self-hostable replacement for online certificate decoders.
Live demo: https://pki-toolbox.youkyi.net (the demo is the same static build you can self-host below; nothing you paste leaves your browser).
Every byte is parsed inside your browser with @peculiar/x509
and pkijs. Nothing is ever
uploaded, the backend only ships static files. You do not have to take that on
trust: see Checkable privacy below.
| Tool | Category | Description |
|---|---|---|
| Certificate decoder | Decoding | Every field of an X.509 certificate: subject, issuer, validity, SANs, key usage, fingerprints |
| CSR decoder | Decoding | Subject, public key, signature algorithm and requested extensions of a PKCS#10 request |
| Chain decoder | Decoding | Splits a concatenated PEM bundle into an ordered chain and verifies every issuer-to-subject link |
| CRL decoder | Decoding | Revoked entries, dates and revocation reasons |
| PKCS#7 decoder | Decoding | Every certificate carried by a PKCS#7 / CMS bundle |
| PKCS#12 decoder | Decoding | Opens password-protected .p12 / .pfx files |
| Fingerprints | Inspection | SHA-1 / SHA-256 / SHA-512 of the DER |
| ASN.1 viewer | Inspection | Expandable tag/length/value tree of any DER artefact |
| Format conversion | Conversion | PEM ↔ DER ↔ PKCS#7 |
| Self-signed certificate | Generation | Self-signed certificate and key pair (RSA / EC / Ed25519) via WebCrypto |
| Sign from a CA | Generation | Issues a certificate from an existing CA: new key pair or CSR, leaf or intermediate, with a ready-to-use fullchain |
The catalogue lives in a single registry (src/lib/tools.ts) that drives the
navbar and the home grid.
Around the tools themselves:
src/lib/pki/detect.ts) and opens in the tool
that reads it./ puts the caret in the input.The claim "it never leaves your browser" is verifiable from inside the page:
connect-src directive from the policy it was served with, counts the
requests able to carry data out since you pasted, and runs a live attempt the
browser refuses in front of you, quoting the directive that blocked it.Always run a pinned, immutable release tag (vX.Y.Z), never latest: a moving
tag cannot be audited or rolled back. For the strongest guarantee, pin the
image digest (...@sha256:...).
docker run -p 8080:8080 ghcr.io/youkyi/pki-toolbox:v2.2.0
Then open http://localhost:8080.
Each release is published, under the same immutable vX.Y.Z tag, to:
| Registry | Image |
|---|---|
| GitHub Container Registry | ghcr.io/youkyi/pki-toolbox |
| Docker Hub | youkyi/pki-toolbox |
docker pull ghcr.io/youkyi/pki-toolbox:v2.2.0 # or
docker pull youkyi/pki-toolbox:v2.2.0
services:
pki-toolbox:
image: ghcr.io/youkyi/pki-toolbox:v2.2.0
ports:
- '8080:8080'
restart: unless-stopped
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
read_only: true
docker compose up -d
The repository ships a fully hardened docker-compose.yml (read-only root
filesystem, dropped capabilities, memory and CPU limits); use that file
directly rather than this minimal example.
The image is built from nginx:alpine-slim, weighs about 25 MB, listens on the
non-privileged port 8080 and runs as a non-root user.
Requires Node 24 (the version the image and CI build with) and pnpm 11.
The exact pnpm version is pinned by the packageManager field of
package.json, so corepack enable is enough to get it.
pnpm install
pnpm dev # dev server on http://localhost:5173
pnpm test # unit tests, single run (Vitest)
pnpm test:unit # the same tests, in watch mode
pnpm test:e2e # end-to-end tests (Playwright)
pnpm test:e2e:ui # the same, in the Playwright UI
pnpm check # svelte-check / TypeScript
pnpm lint # Prettier + ESLint
pnpm format # rewrite files with Prettier
pnpm build # static build into ./build
pnpm preview # serve ./build locally
docker build -t pki-toolbox .
docker run -p 8080:8080 pki-toolbox
adapter-static to plain
HTML/JS: every route is prerendered and there is no Node runtime in
production.src/lib/pki/, with no
Svelte import: parse.ts, chain.ts, crl.ts, pkcs7.ts, pkcs12.ts,
asn1.ts, convert.ts, format.ts, pem.ts, oids.ts, detect.ts,
generate.ts and . Each route under is a thin UI
shell around them.<meta> tag with connect-src 'none'; nginx adds
frame-ancestors 'none', X-Frame-Options, X-Content-Type-Options,
Referrer-Policy: no-referrer, a restrictive Permissions-Policy and the
cross-origin isolation headers. The Vercel demo sends the same header set
(see vercel.json).docker-compose.yml adds a read-only root filesystem, cap_drop: ALL,
no-new-privileges and resource limits.This project follows Semantic Versioning and
Conventional Commits. Notable changes
are recorded in CHANGELOG.md. Contribution rules live in
CONTRIBUTING.md.
CHANGELOG.md, rename the ## [Unreleased] heading to
the new version with today's date, e.g. ## [1.2.3] - 2026-06-01, add a
fresh empty ## [Unreleased] above it, and update the link references at
the bottom of the file."version" in package.json to the same number.main (a conventional commit, e.g. chore(release): 1.2.3) and
push.git tag -a v1.2.3 -m "pki-toolbox v1.2.3"
git push origin v1.2.3
Lint, unit tests and end-to-end tests run on every push. The scan, build and
release stages come from the shared YouKyi-Infra/ci-infrastructure/ci-catalog
components (trivy-scan, docker-build, gitlab-release), pinned by version
in .gitlab-ci.yml.
A vX.Y.Z tag runs the whole chain: scan, image build, image scan, push of
vX.Y.Z to the GitLab registry and to the public mirrors (ghcr.io, Docker
Hub), then a GitLab Release whose notes are the matching ## [X.Y.Z] section
extracted from CHANGELOG.md (an empty section fails the job rather than
publishing empty notes).
A push to main runs the same pipeline minus the release step, and the image
job runs only when a file affecting the build changed, so a docs- or
config-only push does not rebuild the image.
Dependencies are kept up to date by Renovate,
which extends the shared Renovate-Bot/renovate-config preset
(see renovate.json).
sign.tssrc/routes/@peculiar/x509 v2 builds its objects through tsyringe, which needs a
Reflect metadata polyfill on the consumer side, so @abraham/reflection is
imported ahead of it in every module that uses the library.pkijs, whose Web Crypto engine is registered
once in src/lib/pki/engine.ts.tests/pki/ (Vitest) and browser tests in e2e/
(Playwright). Fixtures are real public roots (ISRG Root X1/X2) plus a
generated EC chain and CSR, see scripts/generate-fixtures.mjs.trivy fs