Skip to content

Security: change key passphrases in-process instead of via ssh-keygen - #41

Merged
frequency403 merged 3 commits into
developmentfrom
security/in-process-passphrase-change
Sep 28, 2026
Merged

frequency403 merged 3 commits into
developmentfrom
security/in-process-passphrase-change

Conversation

@frequency403

Copy link
Copy Markdown
Owner

Summary

SshKeyManager.ChangePasswordOfKeyAsync started ssh-keygen -p -f {path} -P "{old}" -N "{new}".

  • Passphrases were exposed: the old and new passphrase were visible in /proc/<pid>/cmdline and in process listings (ps, WMI), so other local users and processes could read them.
  • Argument injection: a " in a passphrase or a space in the key path broke the argument parsing. That produced a wrong passphrase or passed additional ssh-keygen arguments.

The decrypted key is already loaded in memory. It is now re-encrypted with SshNet.Keygen (ToOpenSshFormat/ToPuttyFormat with SshKeyEncryptionAes256, or SshKeyEncryptionNone for an empty passphrase) and written back in its original format through KeyFileWriterService. The detour through OpenSSH for PuTTY keys and the runtime dependency on ssh-keygen are both gone. Only the private key file is rewritten; the .pub file does not change when the passphrase changes.

Depends on #40: rewriting relies on FileMode.Create truncating the file. Without that, bytes of the old key stay at the end when the passphrase is removed. The base is therefore security/file-access-hardening; once #40 is merged, GitHub retargets this PR to development.

Changes Made

  • Bug fix (non-breaking change)
  • New feature (non-breaking change)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Refactoring (no functional change)

Testing Performed

  • Tested on Windows (Version: _______)
  • Tested on macOS (Version: _______)
  • Tested on Linux (unit tests only, .NET SDK 10.0.112)
  • Added unit tests (if applicable)
  • Verified no regression in existing functionality

Test Evidence

  • New file SshKeyManagerPasswordTests with 3 tests:

    • set a new passphrase, including one with ", -N and spaces;
    • remove the passphrase.

    Each test checks the result on disk by loading the file with PrivateKeyFile.

  • All 210 tests pass. There is no ssh-keygen in the test environment, so the old implementation fails all three tests. That run does not show the quoting issue in isolation.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my code
  • Commented difficult areas
  • Updated documentation (if needed)
  • No new warnings/errors introduced
  • All builds pass locally (dotnet build, dotnet test)

Notes for Reviewers:

  • Priority: high
  • Breaking change: no

🤖 Generated with Claude Code

https://claude.ai/code/session_01XSUJwZ17AWMdDvYXboJAbQ


Generated by Claude Code

Local:
- KeyFileWriterService: truncate on overwrite (FileMode.Create), CreateNew
  otherwise, FileShare.None, and enforce 0600 on overwritten files since
  UnixCreateMode only applies on creation.
- Create the backup directory (holds private key copies) and ~/.ssh with 0700.

Remote:
- Read/write authorized_keys and known_hosts via SFTP instead of
  shell command lines built from server-provided paths and file contents
  (broken quoting on Windows for entries containing '"', e.g. command="...").
- No more eager file creation on connect; missing files read as empty and
  are created with 0600 (dir 0700) on Unix hosts when written.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XSUJwZ17AWMdDvYXboJAbQ
ssh-keygen was invoked with -P "<old>" -N "<new>" on the command line:
both passphrases were readable by other local users (/proc/<pid>/cmdline,
process listings) and quotes/spaces in passphrase or path broke argument
parsing, allowing additional ssh-keygen arguments to be injected.

The decrypted key is already loaded, so it is now re-encrypted with
SshNet.Keygen and written back in its original format (OpenSSH, PuTTYv2,
PuTTYv3). This also removes the OpenSSH round-trip for PuTTY keys and the
runtime dependency on ssh-keygen being on PATH.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XSUJwZ17AWMdDvYXboJAbQ
Base automatically changed from security/file-access-hardening to development September 28, 2026 12:51
@frequency403
frequency403 merged commit 64b3594 into development Sep 28, 2026
3 checks passed
@frequency403
frequency403 deleted the security/in-process-passphrase-change branch September 28, 2026 12:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants