did-git-sign
A standalone CLI tool that signs git commits using DID Ed25519 keys managed by a Verifiable Trust Agent (VTA). It acts as a git SSH signing proxy — no private key material ever touches disk.
How It Works
Git supports pluggable signing programs via gpg.ssh.program. When you commit,
git calls did-git-sign with the commit data on stdin. The tool:
- Loads its config (
.did-git-sign.json) and retrieves the VTA credential from the OS keyring - Authenticates with the VTA (or reuses a cached token)
- Fetches the Ed25519 signing key from the VTA on-the-fly
- Produces an SSH signature (PROTOCOL.sshsig format) and writes it to stdout
- Zeroizes the key material from memory
Your DID verification method ID (e.g. did:webvh:abc:example.com#key-0) is used
as the git user.email, linking every commit to your decentralized identity.
Prerequisites
- A running VTA with your persona DID(s) and Ed25519 signing key(s) provisioned.
- Your VTA DID (e.g.
did:webvh:scid:your-vta.example.com).initdiscovers the service URL from the DID document, mints a short-lived admin did:key for the setup session, and prints thepnm contexts createcommand to authorise it.
Install
did-git-sign is the signing half of
Verifiable Git Infrastructure (VGI);
the CI verifier is the separate verify-trust crate.
Setup
Per-repository
init resolves the VTA, mints a temporary admin did:key, and prints a
pnm contexts create … command. Run it in your Personal Network Manager to
authorise the setup session, press Enter, then select the persona and signing
key interactively. This writes .did-git-sign.json in the current directory
and configures the local git repo.
Global (all repositories)
Saves config to ~/.config/did-git-sign/ and sets global git config.
Non-interactive
Name the persona and key to skip the picker (and --yes to skip the
"press Enter once authorised" prompt):
Options
| Flag | Description |
|---|---|
--vta-did |
VTA DID; the service URL is discovered from its document (required) |
--context |
Context id to provision into (default did-git-sign) |
--key-id |
VTA key id for the signing key (skips interactive selection) |
--did-key-id |
DID verification-method id to sign as (skips interactive selection) |
--name |
Git user.name (optional) |
--vta-url |
Override the VTA URL instead of resolving it from the DID |
--global |
Configure global git instead of per-repo |
--yes |
Assume the admin grant is already registered; skip the prompt |
What init configures
The init command performs the following:
- Saves config to
.did-git-sign.json(local) or~/.config/did-git-sign/config.json(global) — contains onlykey_id,did_key_id, anduser_name - Stores VTA credentials (URL, DIDs, private key) in the OS keyring (macOS Keychain / Linux Secret Service)
- Verifies VTA connectivity by authenticating and fetching the signing key
- Configures git:
gpg.format = sshgpg.ssh.program = did-git-signgpg.ssh.defaultKeyFile = <config path>commit.gpgsign = trueuser.email = <DID#key-id>user.name = <name>(if provided)
- Creates an
allowed_signersfile for signature verification and setsgpg.ssh.allowedSignersFile
Usage
After setup, commits are signed automatically:
Verify signatures:
Check your configuration and VTA connectivity:
Showing names instead of DIDs
init's context and DID pickers label each entry with a human name where the
VTA has one — an ACL label, or the context's own name — falling back to an
abbreviated DID. Pass --resolve-agent-names to init or health to also
read back the agent name a DID document claims (example.com/@alice):
Each claimed name is resolved forward and must lead back to the DID that claims
it before it is shown as that DID's; a claim that does not round-trip is tagged
[unverified], because alsoKnownAs is self-asserted and an unchecked name is
only what a DID says about itself. Resolution costs an outbound HTTPS fetch per
claimed name, so it is opt-in. Names never replace the DID in a summary or
diagnostic — they are printed above it.
Selecting which community persona signs
With more than one provisioned persona, you can choose which one signs without
re-running init. At sign time the signing key is resolved in this order:
- The
DID_GIT_SIGN_KEYenvironment variable (per-invocation override). - The
did-git-sign.keyper-repo git config setting. - The
did_key_idin the config file git points at (theinitdefault).
The value is the persona's did:webvh:…#key-N. It must have credentials stored
in the keyring (i.e. you ran init for that persona); otherwise signing fails
with a clear message rather than silently signing as a different persona.
# One commit as a specific persona:
DID_GIT_SIGN_KEY=did:webvh:abc:example.com#key-1
# Pin a persona for this repository:
Security Model
- No key material on disk — the VTA credential private key is stored in the OS keyring, and the Ed25519 signing key is fetched from the VTA at sign-time and held only in memory.
- Token caching — the VTA access token is cached in the OS keyring to avoid re-authentication on every commit. Tokens are validated with a 30-second safety margin before reuse.
- Zeroization — signing key material is zeroized immediately after use via
the
zeroizecrate. DID_GIT_SIGN_SSH_KEYGENoverride is test-only. The path tossh-keygenused for the verify / find-principals / check-novalidate delegation paths can be overridden via this environment variable so test fixtures can point at a mock binary. Do not set it in production. An attacker with write access to your environment could redirect signature verification to a binary that always returns success and silently accept forged signatures. The override has no effect on the signing path, which never invokesssh-keygen.
Architecture
git commit
|
v
git calls: did-git-sign -Y sign -f .did-git-sign.json -n git
| (stdin: commit data)
v
did-git-sign:
1. Load config from .did-git-sign.json (key_id + did_key_id only)
2. Load VTA credentials from OS keyring
3. Authenticate with VTA (or use cached token from keyring)
4. Fetch Ed25519 key: VTA.get_key_secret(key_id)
5. Sign commit data (PROTOCOL.sshsig format)
6. Output SSH signature to stdout
7. Zeroize key material
|
v
git stores signature in commit
Config File Format
The .did-git-sign.json file contains only your DID identity:
No VTA credentials or key identifiers are stored on disk. All VTA
configuration and sensitive material is stored in the OS keyring under the
service name did-git-sign:
| Keyring Entry | Contents |
|---|---|
{did_key_id}:vta |
VTA URL, VTA DID, credential DID, credential private key, signing key ID |
{did_key_id}:token |
Cached VTA access token and expiry |