Desktop & Extension Viewers
Use VerifyKit's React or vanilla viewer with the CRL/OCSP revocation plugin inside Electron, Tauri, and browser-extension hosts. In a normal web page the browser blocks direct CRL/OCSP requests, so online revocation needs a server-side proxy. In these hosts you have a native network layer instead — so you can drop the proxy and let the app itself perform the fetch.
Why a bridge is needed (not a proxy server)
VerifyKit's revocation logic (checkCRL / checkOCSP) is already pure JavaScript and runs anywhere. What a plain web page cannot do is fetch the CRL/OCSP endpoint, because of three browser walls:
- CORS — CA revocation servers do not send
Access-Control-Allow-Origin, so the browser blocks the response. - Mixed content — many CRL/OCSP URLs are
http://, blocked from anhttps://page. - OCSP preflight — OCSP is a
POSTwith a non-simple content type, triggering a CORS preflight the servers don't answer.
The fix is never "move more code to a server" — it is "run the fetch in a context without CORS." Electron's main process, Tauri's Rust backend, and an extension's background worker are exactly such contexts. You keep the viewer plugin in the front end and bridge only the network call.
Electron
Run the revocation check in the main process (Node — no CORS) and bridge to it over IPC. No external server needed.
Main process — expose the shipped handler:
// electron/main.ts
import { ipcMain } from 'electron'
import { processRevocation } from '@trexolab/verifykit-plugin-revocation/handler/core'
ipcMain.handle('vk:revocation', (_event, payload) => processRevocation(payload))Preload — expose a typed bridge to the renderer:
// electron/preload.ts
import { contextBridge, ipcRenderer } from 'electron'
import type { RevocationRequest, RevocationCheckResult } from '@trexolab/verifykit-plugin-revocation'
contextBridge.exposeInMainWorld('vkRevocation', {
check: (payload: RevocationRequest): Promise<RevocationCheckResult> =>
ipcRenderer.invoke('vk:revocation', payload),
})Renderer — a tiny custom plugin that forwards to the bridge, then pass it to the viewer:
import type { VerifyKitPlugin } from '@trexolab/verifykit-core'
import type { RevocationCheckResult } from '@trexolab/verifykit-plugin-revocation'
declare global {
interface Window {
vkRevocation: { check(payload: unknown): Promise<RevocationCheckResult> }
}
}
const revocationBridgePlugin: VerifyKitPlugin = {
name: 'revocation',
revocation: {
checkCRL: (cert, urls) => window.vkRevocation.check({ type: 'crl', cert, urls }),
checkOCSP: (cert, issuer, urls) => window.vkRevocation.check({ type: 'ocsp', cert, issuer, urls }),
},
}
// React: <VerifyKitProvider config={{ plugins: [revocationBridgePlugin] }}>
// Vanilla: VerifyKit.create(el, { plugins: [revocationBridgePlugin] })Avoid
webPreferences: { webSecurity: false }to "make fetch work" — it disables same-origin protection app-wide. The IPC bridge is safer and needs no external server.
Tauri
Tauri's webview enforces CORS like any browser, so route the fetch through the Rust backend.
Option A — global fetch swap (simplest). @tauri-apps/plugin-http provides a fetch that runs in Rust (no CORS). Install it, then route all fetch through it once at startup; the built-in revocationPlugin() (direct mode, no endpoint) then works unchanged:
// main.tsx — run once, before creating the verifier
import { fetch as tauriFetch } from '@tauri-apps/plugin-http'
globalThis.fetch = tauriFetch as typeof globalThis.fetch
import { revocationPlugin } from '@trexolab/verifykit-plugin-revocation'
const plugins = [revocationPlugin()] // no endpoint → direct, now CORS-freeAlso allow the CRL/OCSP hosts in your Tauri capabilities (src-tauri/capabilities/*.json, the http:default permission scope) and in the tauri.conf.json CSP connect-src.
Option B — Rust command (most robust). Do the fetch and parse in Rust via a #[tauri::command], then invoke it from a custom plugin. This also closes a security gap: the JS online checker does not verify CRL/OCSP response signatures, whereas a Rust command can reuse the core engine's parser. The JS side:
import { invoke } from '@tauri-apps/api/core'
import type { VerifyKitPlugin } from '@trexolab/verifykit-core'
const revocationBridgePlugin: VerifyKitPlugin = {
name: 'revocation',
revocation: {
checkCRL: (cert, urls) => invoke('vk_check_revocation', { req: { type: 'crl', cert, urls } }),
checkOCSP: (cert, issuer, urls) => invoke('vk_check_revocation', { req: { type: 'ocsp', cert, issuer, urls } }),
},
}The Rust vk_check_revocation command fetches each URL (e.g. with reqwest) and returns { status: 'good' | 'revoked' | 'unknown', ... }. Reusing the core revocation parser requires factoring packages/core/src/verify/revocation_checker.rs into a shared non-WASM crate — worth it when you need signature-verified revocation.
Browser extension (MV3)
An extension's background service worker can fetch cross-origin for any host listed in host_permissions — CORS does not apply there. Run the check in the background and message it from the viewer.
Manifest — declare host access:
{
"manifest_version": 3,
"host_permissions": ["http://*/*", "https://*/*"],
"background": { "service_worker": "background.js" }
}Background — reuse the shipped handler:
// background.ts
import { processRevocation } from '@trexolab/verifykit-plugin-revocation/handler/core'
chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
if (msg?.kind === 'vk:revocation') {
processRevocation(msg.payload)
.then(sendResponse)
.catch(() => sendResponse({ status: 'unknown' })) // fail-open on a bad payload
return true // keep the channel open for the async response
}
})Viewer (extension page or content script) — bridge plugin:
import type { VerifyKitPlugin } from '@trexolab/verifykit-core'
const send = (payload: unknown) => chrome.runtime.sendMessage({ kind: 'vk:revocation', payload })
const revocationBridgePlugin: VerifyKitPlugin = {
name: 'revocation',
revocation: {
checkCRL: (cert, urls) => send({ type: 'crl', cert, urls }),
checkOCSP: (cert, issuer, urls) => send({ type: 'ocsp', cert, issuer, urls }),
},
}Security notes
- These bridges are fail-open, like the HTTP proxy: an unreachable revocation server leaves the signature's revocation status
unknown(shown as "validity unknown"), never a false "valid". See Revocation Checking and Security. processRevocation's default SSRFurlFilterblocks common private/internal hosts (10.x,192.168.x,172.16–31.x,*.internal,*.local,localhost), but it does not cover cloud-metadata / link-local ranges (e.g.169.254.169.254, IPv6fe80::/10andfc00::/7) and it allows all other public hosts. For an internal/enterprise PKI — or to block cloud-metadata endpoints — pass a customurlFilter(allowlist) in the handler options.- The JavaScript online checker does not verify the CRL/OCSP response signature. Tauri Option B (Rust parser) is the only path here that closes that gap.
Which platform needs what
| Host | Where the fetch runs | What to add |
|---|---|---|
| Browser (web page) | — (blocked by CORS) | A proxy server (handleRevocation) or embedded LTV/DSS |
| Electron | Main process (Node) | ipcMain → processRevocation + preload bridge + custom plugin |
| Tauri | Rust backend | plugin-http global-fetch swap (A) or a Rust command (B) |
| Browser extension | Background service worker | host_permissions + chrome.runtime message + custom plugin |
| Node / SSR | In-process | revocationPlugin() direct mode (no bridge) |