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
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 (featurewinstore, Windows only).Pkcs11Picker— a cross-platform Iced picker for PKCS#11 tokens (featurepicker).
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| Option | Effect |
|---|---|
.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 hwndFor 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 flag | UI response |
|---|---|
CKF_USER_PIN_COUNT_LOW — a few attempts remain | amber advisory (pin_attempts_low) |
CKF_USER_PIN_FINAL_TRY — last attempt before lock | red warning (pin_final_try) |
CKF_USER_PIN_LOCKED — already locked | input 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.dllWhich 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.
Use Pkcs11Picker — there is no native PIN prompt for PKCS#11 tokens, so the SDK provides the
cross-platform UI. It also runs on Windows if you want one consistent token UI across every OS.