fix(keychain): derive a key's public half instead of refusing to deploy - #161
Merged
Conversation
"Add to host" read `key:<id>:public` and threw "Public key not found" when nothing was stored. The public half is optional at import, and nothing ever derived it, so a key pasted or dropped private-only saved without complaint and refused to deploy — often weeks later. A new backend command computes the public half from the private one (`ssh_public_key_from_private`, error codes ENCRYPTED / INVALID so the UI can tell "needs a passphrase" from "not a key"). `ensurePublicKey` reads the stored half, else derives from the private half and the saved passphrase, validates it, and backfills both the local vault and the key's team vault. - KeyForm and IdentityForm's inline key material fill the visible public field as soon as a complete private half is entered, so the value can be read and corrected rather than conjured at deploy time. - addKeyToHost goes through the store, which fixes the plugin/MCP route too. - The export panel asks before a host is picked: a key with no derivable public half shows a warning and a disabled button, instead of failing after the host was chosen. - The outcome no longer renders at the bottom of the scroll area, where a click looked like it did nothing: failures pin into the sticky footer above the button, and success raises a toast and closes the panel.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
"Add to host" fails with "Public key not found" on any key that was imported private-only. The public half is optional at import and nothing derived it, so the key saves without complaint and refuses to deploy later.
addKeyToHostreadkey:<id>:publicand threw when it was absent (src/services/keyExport.ts). The export panel offered the action for any key and only reported the problem after a host was picked and the button pressed — and the message rendered at the bottom of the scroll area, below the fold, so the click looked like it did nothing.The fix
Derivation. New backend command
ssh_public_key_from_private(src-tauri/src/commands/keygen.rs) computes the public half from the private one, decrypting with a passphrase when there is one. It fails with codes —ENCRYPTED/INVALID— so the UI can tell "give me the passphrase" from "that is not a key" without matching on message text.ensurePublicKey(src/services/publicKeyStore.ts) reads the stored half, else derives from the private half plus the saved passphrase, validates the result withisValidSshPublicKey, and backfills both the local vault and the key's team vault.KeyFormandIdentityForm's inline key material fill the visible public field as soon as a complete private half is entered (shareduseDerivedPublicKeyhook), so the value can be read and corrected instead of being conjured at deploy time.addKeyToHostnow goes through the store — this fixes the plugin/MCPkeys.addToHostroute too.Export panel UX.
max-h-20, scrolls) instead of the scroll area.New strings in en / fr / ru / zh.
Verification
cargo test --lib335 passed ·vitest run486 files / 3778 tests passed ·tsc --noEmitclean ·cargo fmt --checkclean.New tests: 4 Rust (plaintext + encrypted round-trip, ENCRYPTED, INVALID), 8 for
publicKeyStore, 4 forKeyExportPanel, 4 for the forms.Live-run against the headless stack and a real
ssh-host-1:.pub.authorized_keysgained the derived line, andkey:<id>:publicis stored afterwards.Deliberately out of scope
key:<id>:publicraw; they copy an absent public half rather than deriving one.{ id: "system" | "core", name: "Voltius" }toast literals are left alone — the source id drives grouping, so unifying them is its own change.