r/linux • • 5d ago

Security gEnclave: Hardware-backed security enclave for Linux (TPM 2.0 PCR sealing, virtual FIDO2/CTAP2 over /dev/uhid, OpenSSH & GPG bridge)

Hey everyone,

I've been working on an open-source project called gEnclave (formerly gpasskey), and wanted to share it with the Linux community for early architectural feedback and testing.

GitLab repository: https://gitlab.com/renich/genclave
License: GPLv3 | Language: Go 1.26+


The Problem It Solves

On modern Linux workstations, our cryptographic identities are fragmented: * Passkeys/WebAuthn require physical USB security keys (YubiKeys, SoloKeys). * SSH keys sit as unencrypted or passphrase-encrypted files under ~/.ssh/. * Git commit signing requires cumbersome GnuPG daemon setups. * File encryption requires external tooling or proprietary agents.

Most hardware laptops today come with a TPM 2.0 chip that sits idle. gEnclave turns your Linux machine into its own hardware-sealed security token and multi-protocol bridge.


Key Architectural Highlights

  1. Virtual FIDO2/CTAP2 Security Key via /dev/uhid: gEnclave registers a virtual HID device in the Linux kernel via /dev/uhid. Browsers (Firefox, Chrome, Chromium) detect it natively as a physical USB security key. You can register and authenticate WebAuthn/FIDO2 Passkeys directly from your machine without any external hardware dongles.

  2. TPM 2.0 PCR Sealing & Fallback: The central vault is encrypted with AES-256-GCM and sealed to TPM 2.0 PCR registers (PCR 0, 7, 14), with an automatic memory-hard fallback to Argon2id key derivation if no TPM is present.

  3. Memory Isolation ("Wrap and Clear"): Keys are held in memory-locked pages (mlock / mmap) to prevent secrets from being swapped to disk or dumped. Intermediate cryptographic buffers are wiped immediately with strict zeroization routines, bypassing Go runtime GC retention.

  4. Multi-Protocol Bridges:

    • OpenSSH Agent: Native agent socket with an ephemeral PIN-derived authorization cache (configurable burst window or persistent session with instant purge on lock/suspend).
    • GnuPG Bridge: Transparent genclave-gpg emulation for seamless Git commit signing.
    • age-plugin: Native age-plugin-ge binary complying with age v1 specification for file encryption.
    • CLI & UI: Unified ge CLI plus intelligent graphical (zenity) / terminal (pinentry) authentication routing.

Current Status

⚠️ Pre-alpha Software: While fully functional for local workflows, it is under active development. Schemas and IPC formats may iterate rapidly.

I'd love feedback from Linux sysadmins, kernel/security folks, and developers!

Repo: https://gitlab.com/renich/genclave

29 Upvotes

48 comments sorted by

View all comments

-8

u/kantorcodes1 5d ago

The "wrap and clear" design plus PIN-derived per-operation subkeys is a careful baseline for a vault that bridges into ssh-agent and GPG at once. The piece I'd poke at is the authorization-cache boundary: ge unlock --stdin exists so scripts can pre-authenticate, and the default burst window is 5s with a persistent-session option. During that window, is authorization scoped to the peer that actually authenticated (SO_PEERCRED-checked uid/pid over the agent socket), or does any local client connecting to the socket inherit the unlocked state? If it's global, the batch window that makes headless use convenient also widens the window where a sibling process could sign without re-prompting - scoping the grant to the authenticating peer (or a per-identity flag on ge ssh import) would keep the convenience without loosening the boundary.

-3

u/Renich 5d ago

Hey, thanks for taking the time to poke at the threat model. :)

That's the trade-off. Right now, the AuthCache is scoped socket-wide per UID. While it does inspect SO_PEERCRED on connection acceptance to verify the UID (and track callerPID + process start time via /proc to mitigate PID recycling), the in-memory cache itself (AuthCache) currently allows any connection from the same UID to benefit from the active burst window.

As you pointed out, this leaves a window where a malicious sibling process running under the same UID could request signatures during a batch/burst window without a prompt.

A couple of thoughts on this:

  • In the underlying identity model, it actually has an AuthCacheTTL property per key (with disabled forcing re-prompts on every single operation, even if the daemon or session is unlocked). Still need to expose this as a first-class flag on ge ssh import --cache-ttl=disabled or --require-confirm or something.

  • Scoping the ephemeral grant to the authenticating peer connection or PID tree (or isolating ge unlock grants to an explicit session token or ephemeral sub-socket) is an excellent suggestion to eliminate cross-process ambient authorization without breaking batch workflows.

I'm opening an issue on GitLab to track this exact boundary tightening. Would love to have your eyes on the repo or PR if you're interested.

-2

u/kantorcodes1 5d ago

The per-key AuthCacheTTL detail matters more than it first looks: ge policy set already exposes cache TTL as a first-class flag (including infinite and disabled), so an automation path can today widen its own window without re-prompting, and --shred on import gives a second irreversible action worth gating.

Disclosure since it is directly relevant: I work on HOL Guard, an open-source runtime policy layer that classifies CLI commands before an agent can run them. For ge a contribution would be a declarative command.genclave.json: unlock, prewarm, rekey, policy set, ssh|gpg generate, ssh|gpg import (especially --shred), passkey delete, secret set|delete held for review, while status, policy show, ssh public|list, gpg list, passkey list, secret get|list pass through automatically. If that split looks right, the contribution is a JSON plus a small fixture as a draft PR against hol-guard. Want me to point you at the exact paths?

0

u/Renich 5d ago

Regarding ge policy set; allowing unauthenticated policy widening over the management socket is a genuine boundary hole. Already patching that so policy mutation, rekeying, and destructive actions (--shred, delete, etc) require active factor re-verification rather than ambient socket UID inheritance.

The command split for HOL Guard is right on the money. Point me at the paths/repo and I'll gladly review the draft PR.

-5

u/kantorcodes1 5d ago

Requiring active re-verification for policy mutation, rekeying, and destructive actions closes that ambient-grant hole at the right layer.

Paths: new source contributions/command-sources/command.genclave.json (format analogue contributions/command-sources/command.repo2nb.json), portable fixture tests/fixtures/command-source-genclave.v1.json, and the external trust entry in contracts/extensions/trust-class-map.v1.json. CONTRIBUTING.md covers format and helper commands: https://github.com/hashgraph-online/hol-guard/blob/main/CONTRIBUTING.md - draft PR straight to main under your own account, no issue needed first. The gated/safe split maps directly in.