Mudrit
Guides

Certificate Pickers

Let a user choose a signing certificate — the native Windows dialog or the cross-platform Iced picker — and why the two differ by design

Part of mudrit-keystore

When a person signs, they usually need to choose a certificate and unlock it. Mudrit ships two pickers, each matched to how its platform handles the trust ceremony:

  • WinStorePicker — the native Windows certificate dialog (feature winstore, Windows only).
  • Pkcs11Picker — a cross-platform Iced picker for PKCS#11 tokens (feature picker).

Both return an unlocked Signer you hand straight to sign_pdf, and both return Error::Cancelled if the user dismisses them.

Why two pickers instead of one?

The asymmetry is deliberate. On Windows the OS/provider owns the trusted ceremony: certificate selection and the PIN prompt (with wrong-PIN count and lockout) are native, so re-implementing them in the SDK would only reduce trust. On Linux and macOS there is no native PIN prompt for PKCS#11 tokens, so the SDK must provide its own cross-platform UI. Use the native path on Windows; use the SDK-owned Iced picker where there is nothing native to defer to.

WinStorePicker — native Windows dialog

WinStorePicker shows the OS "Windows Security" selection dialog over the certificates that pass your CertFilter, and returns a WinStoreSigner. The PIN prompt fires at sign time, from the CSP/KSP — the selection UI never handles it, and Mudrit never sees the PIN.

use mudrit::prelude::*;

let signer = WinStorePicker::new()
    .filter(CertFilter::default().signing_only(true)) // which certs appear
    .title("Acme Sign — choose your certificate")     // dialog title
    .prompt("Select your signing DSC / token:")       // instruction line
    .parent_hwnd(hwnd)                                 // owner window → modal, never opens behind your app
    .auto_select_single(true)                          // exactly one match → skip the dialog entirely
    .select()?;                                        // Error::Cancelled if dismissed

println!("selected: {} / {}", signer.subject().0, signer.subject().1);
let signed = sign_pdf(&pdf, &signer, &cfg)?;           // native PIN prompt appears here
OptionEffect
.filter(CertFilter)Which certificates the dialog lists (e.g. .signing_only(true)).
.title(&str)Window title.
.prompt(&str)Instruction line shown above the list.
.parent_hwnd(isize)Owner window handle → the dialog is modal to your app, never opens behind it.
.auto_select_single(bool)Exactly one match → skip the dialog and use that certificate.

Fewer clicks for single-DSC users

auto_select_single(true) is ideal for locked-down enterprise machines where each user has exactly one signing certificate — the dialog is skipped and that certificate is used straight away.

Inspecting a certificate

When you build your own selection list over WinStoreKeyStore::aliases(), you can pop the native certificate-properties viewer for any DER certificate. This needs the opt-in winstore-viewer feature (it pulls the heavier GDI / WinTrust bindings), so the base winstore backend stays lean.

use mudrit::mudrit_keystore::view_certificate;

view_certificate(signer.certificate(), None)?; // OS "certificate properties" dialog; None = no parent hwnd

For a cross-platform, zero-extra-dependency alternative, render your own view from parse_certificate()CertDetails instead of the native viewer.

Pkcs11Picker — cross-platform Iced picker

Pkcs11Picker opens a themed selection window over one or more PKCS#11 modules, then a separate PIN window, and returns the unlocked Signer. It mirrors WinStorePicker's shape and is fully brandable and localizable.

use mudrit::mudrit_keystore::{Pkcs11Picker, PickerLabels};

let signer = Pkcs11Picker::new(modules)                // Vec<String> of module paths (.dll / .so / .dylib)
    .title("Acme Sign — choose your certificate")      // selection-window title
    .pin_title("Acme Sign — unlock token")             // PIN-window title
    .icon_rgba(w, h, rgba)                             // window icon from raw RGBA (decode your PNG first)
    .labels(PickerLabels {
        ok: "Sign".into(),
        cancel: "Dismiss".into(),
        ..Default::default()                           // English defaults for the rest
    })
    .select()?;                                        // Box<dyn Signer>, Error::Cancelled if dismissed

let signed = sign_pdf(&pdf, signer.as_ref(), &cfg)?;

The picker follows the OS light/dark appearance, groups certificates by token, and offers live search, a "signing only" filter, and a "hide expired" filter.

Two windows, one at a time

The selection and PIN steps are true separate windows shown one at a time — a Windows-style select → PIN flow. The selection window closes as the PIN window opens, so the two never overlap.

Live plug / remove

The token list stays fresh while the picker is open: plug a token in or pull it out and the list updates live, so a user who forgot to insert their token can do it without restarting the flow.

Proactive PIN status

Because the SDK owns C_Login here, the PIN window surfaces the token's retry state before the user even types, from the polled token flags:

Token flagUI response
CKF_USER_PIN_COUNT_LOW — a few attempts remainamber advisory (pin_attempts_low)
CKF_USER_PIN_FINAL_TRY — last attempt before lockred warning (pin_final_try)
CKF_USER_PIN_LOCKED — already lockedinput disabled, clear message (pin_locked)

PKCS#11 exposes only these three flags — an exact remaining count is not portable across tokens.

Branding & localization with PickerLabels

Every user-facing string lives in PickerLabels, so one struct rebrands and translates the whole UI. .title(...) and .pin_title(...) are shortcuts for the two window titles; pass a full PickerLabels for the rest.

Prop

Type

.icon_rgba(width, height, rgba) takes raw RGBA pixels (width * height * 4 bytes) — decode your PNG/logo to RGBA first. Invalid dimensions are ignored and the default icon stays. The picker's signer uses RSA (PKCS#1 v1.5 or PSS via .with_algorithm); for an ECDSA token, open it through the Pkcs11Signer backend directly.

pick_and_sign — the one-call convenience

The mudrit facade bundles the picker and the engine into a single call (feature picker): it opens the Iced picker, and on a successful pick signs input with the chosen, unlocked signer — returning Ok(None) on cancel so you can branch cleanly.

// mudrit, with the `picker` feature
if let Some(signed) = mudrit::pick_and_sign(modules, &cfg, &pdf)? {
    std::fs::write("out.pdf", signed)?;
} else {
    // user cancelled the picker
}

Run the bundled demo (samples/blank.pdf):

cargo run -p mudrit --features picker --example pkcs11_picker -- C:/Windows/System32/eps2003csp11v2.dll

Which one?

Prefer WinStorePicker for certificates in the Windows store (including USB tokens with a minidriver) — you get the native trusted dialog and native PIN handling for free.

Next

On this page