Skip to content

Keypairs & signing

kovra can custody an asymmetric keypair: it generates the key, seals the private half under the vault master key, and never writes the private key to disk or prints it. You operate through kovra — signing, verifying, encrypting, decrypting — without the private key ever leaving the vault.

kovra keygen creates the key and shows only the public half. Choose ed25519 (signing and encryption) or rsa (signing/SSH only):

zsh
~ % kovra keygen secret:dev/ssh/deploy --type ed25519
Generated dev/ssh/deploy (ed25519, Medium).
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDnM/+Nl+/2Y/uEZNK5Q7ZEdQEMmOGXB6PTQg2hWJaSw

The entry lists with a typed mode of keypair:ed25519. Reprint the public key any time — this is free and never prompts:

zsh
~ % kovra pubkey secret:dev/ssh/deploy
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDnM/+Nl+/2Y/uEZNK5Q7ZEdQEMmOGXB6PTQg2hWJaSw

kovra sign signs data from a file or stdin; the signature goes to stdout. kovra verify checks it. Signing with a high/prod key asks you to bioProve it; verifying is always free:

zsh
~ % printf 'release v1.2.3' | kovra sign secret:dev/ssh/deploy > sig.bin
zsh
~ % printf 'release v1.2.3' | kovra verify secret:dev/ssh/deploy --signature sig.bin
OK: signature is valid

Verification also works against a public-only entry — a peer’s public key you stored with kovra add --public-key (it holds no private half), so you can verify a colleague’s signatures without their private key.

Pass --reason to say what the signature is for. It works the same with a file or with stdin:

zsh
~ % kovra sign secret:dev/ssh/deploy --reason "Release manifest v1.2.3" manifest.md > manifest.sig

The reason is one line of at most 200 characters. kovra refuses it with exit code 2, before it opens the key or asks you, when it is empty, longer, or has a line break, a control, format, bidi, combining or invisible character, or a space other than the plain one; letters with accents pass. kovra preflight sign <coord> --reason "<text>" checks it the same way, so an agent learns beforehand that a reason would be refused.

Every signature that asks you — with a high, inject-only or prod key — shows what is signed, with or without --reason:

  • A sentence of kovra’s own: who asks, the file name (or standard input), its size and kind (text, PDF or binary), and the key with its environment. The details (environment, project, secret, who asks, command) follow in grey.
  • The SHA-256 of the exact bytes the key signs, shown short: its first and last six characters, ebbda6 … fb9e9c. Compare them with shasum -a 256 <file> of the file you meant to sign. The document window and the macOS dialog show all 64 characters.
  • A hidden-characters warning (⚠ The document has 2 hidden characters — review it before approving) when a text has characters that change or hide how it reads: bidi controls, zero-width characters, control characters, a carriage return that does not end a line. Review the document before approving.
  • The reason, last, under Reason given by the requester — kovra does not verify it. The caller wrote it, and the caller may be a hijacked agent: read it as a claim, not as a fact.

On the Mac, Review document… opens the document in a separate kovra window. It shows the bytes kovra read and is about to sign: the file is never read again, so changing it afterwards changes nothing.

  • Text (up to 256 KiB): the source, with line numbers, never cut at the edge. Hidden characters show as escapes (\u{202e}), each listed under the header with its line and what it does.
  • Markdown and HTML (a file ending in .md, .markdown, .html or .htm): two tabs, Source and Rendered, and the window opens on Rendered. The source is what is signed: the rendered view can hide content (an HTML comment, a link’s real destination, hidden text), as its banner says. Check Source before approving.
  • PDF (up to 8 MiB): rendered for reading; links, forms and scripts do nothing. A PDF can show less than it contains, so the SHA-256 is the exact statement of what is signed.
  • Binary data, a larger document, or one kovra can’t show (a text too long once escaped, a PDF that does not open or is encrypted): not shown, never even a fragment. The window says why; compare the SHA-256 instead.

You can’t approve while the document window is open: the sensor’s place reads Close the document to approve (without Touch ID, Approve with Touch ID is greyed out). Close it with its close button, ⌘W or Esc, and the review window takes the focus back: the sensor arms on its own after a moment, with no click. If another program took the focus or covered either window while you were reading, click the review window first, as usual. The time to answer keeps running while you read.

On the paired phone, kovra Mobile shows the full SHA-256, the hidden-characters warning when there is one and the reason, and says the document is shown at the Mac only: the document never leaves your Mac. To read it, approve at the Mac.

For an ed25519 keypair, kovra can also encrypt to the public key and decrypt with the private key (an age-based sealed box). Encrypting is free; decrypting with a high/prod key asks you to bioProve it:

zsh
~ % printf 'a short secret note' | kovra encrypt secret:dev/ssh/deploy > ct.bin
zsh
~ % kovra decrypt secret:dev/ssh/deploy ct.bin
a short secret note

RSA keypairs sign and verify (and work as SSH keys) but do not support encryption — use ed25519 when you need sealing.

The private key is generated inside the vault and never materializes on disk or in your shell. An agent or a script can ask kovra to use the key — sign a release, open a sealed message — but cannot exfiltrate it, and every use of a sensitive key is gated and audited. For loading a key into an SSH session, see the governed ssh-agent.