pax_global_header00006660000000000000000000000064151017247010014510gustar00rootroot0000000000000052 comment=2b3c181eaa73ca27b0cfa3fd12148d6b69e35311 keychain-2.9.8/000077500000000000000000000000001510172470100133235ustar00rootroot00000000000000keychain-2.9.8/.gitattributes000066400000000000000000000011331510172470100162140ustar00rootroot00000000000000# Keychain project: enforce consistent Unix LF line endings on all platforms # Normalize all text files to LF on commit and checkout * text eol=lf # (Redundant clarity rules; kept for readability and future maintenance) *.sh text eol=lf *.md text eol=lf *.txt text eol=lf *.pod text eol=lf *.in text eol=lf *.spec.in text eol=lf Makefile text eol=lf # If any binary assets are added later, declare them explicitly, e.g.: # *.png binary # *.jpg binary # *.gif binary # Prevent accidental CRLF introduction in scripts that may be generated scripts/* text eol=lf docs/* text eol=lf keychain-2.9.8/.github/000077500000000000000000000000001510172470100146635ustar00rootroot00000000000000keychain-2.9.8/.github/FUNDING.yml000066400000000000000000000001551510172470100165010ustar00rootroot00000000000000# These are supported funding model platforms github: danielrobbins custom: ["https://paypal.me/breezyops"] keychain-2.9.8/.github/workflows/000077500000000000000000000000001510172470100167205ustar00rootroot00000000000000keychain-2.9.8/.github/workflows/lint.yml000066400000000000000000000004011510172470100204040ustar00rootroot00000000000000name: lint on: [push, pull_request] jobs: shellcheck: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: make keychain run: | make keychain - name: run shellcheck run: | shellcheck keychain keychain-2.9.8/.github/workflows/release.yml000066400000000000000000000042411510172470100210640ustar00rootroot00000000000000name: release on: push: tags: - '[0-9]+.[0-9]+.[0-9]+' jobs: build: runs-on: ubuntu-latest container: image: debian:bookworm-slim steps: - name: Prepare build dependencies run: | apt-get update -y DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ ca-certificates make perl jq git gawk sed gzip tar openssh-client rm -rf /var/lib/apt/lists/* - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Fix git ownership for container run: | git config --global --add safe.directory "$GITHUB_WORKSPACE" - name: Verify tag/version consistency id: ver run: | TAG_NAME="${GITHUB_REF##*/}" FILE_VER=$(cat VERSION) if [ "$TAG_NAME" != "$FILE_VER" ]; then echo "Tag $TAG_NAME does not match VERSION file $FILE_VER" >&2 exit 1 fi echo "version=$TAG_NAME" >> $GITHUB_OUTPUT - name: Build run: | make clean make dist/keychain-$(cat VERSION).tar.gz - name: Test space-in-home handling run: | ver=$(cat VERSION) ./scripts/test-space-home.sh "$ver" - name: Extract changelog section run: | ver=$(cat VERSION) awk -v ver="$ver" '/^## keychain '"$ver"' /{f=1;print;next} /^## keychain / && f && $0 !~ ver {exit} f' ChangeLog.md > .release-notes.md if [ ! -s .release-notes.md ]; then echo "Failed to extract changelog for $ver" >&2 exit 1 fi - name: Upload artifacts (build only; manual publish step remains maintainer-driven) uses: actions/upload-artifact@v4 with: name: keychain-${{ steps.ver.outputs.version }}-artifacts path: | dist/keychain-${{ steps.ver.outputs.version }}.tar.gz keychain keychain.1 .release-notes.md - name: Summary run: | echo 'Artifacts prepared. Use make release or release-refresh locally to publish via API if desired.' >> $GITHUB_STEP_SUMMARY keychain-2.9.8/.gitignore000066400000000000000000000001661510172470100153160ustar00rootroot00000000000000/keychain.1.orig .idea/ keychain.iml keychain keychain.1 keychain.txt keychain.spec .specstory/ .ci-artifacts*/ dist/ keychain-2.9.8/.vscode/000077500000000000000000000000001510172470100146645ustar00rootroot00000000000000keychain-2.9.8/.vscode/settings.json000066400000000000000000000002071510172470100174160ustar00rootroot00000000000000{ "files.eol": "\n", "files.autoGuessEncoding": true, "files.insertFinalNewline": true, "files.trimTrailingWhitespace": true } keychain-2.9.8/COPYING.txt000066400000000000000000000432541510172470100152040ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. keychain-2.9.8/ChangeLog.md000066400000000000000000001216671510172470100155110ustar00rootroot00000000000000# ChangeLog for Keychain - https://github.com/danielrobbins/keychain ## keychain 2.9.8 (2 Nov 2025) This release fixes the release tarball to include all necessary files for building and using keychain. Bug fixes: * Fixed release tarball generation to include bash completion script (`completions/keychain.bash`), Makefile, source files, and other essential components. Previous release (2.9.7) tarball was missing these files. * Improved tarball generation to use `git archive` as source of truth, eliminating manual file inventory and preventing future omissions. * Updated release logic to use `dist/` directory for archive generation. GitHub workflow plumbing work for new `/dist` tarball location, associated `Makefile` and CI fixes. Documentation: * Added bash completion information to keychain man page (NOTES section). ## keychain 2.9.7 (31 Oct 2025) This release fixes critical issues with spaces in HOME directories and usernames, and adds official Git Bash on Windows compatibility. Bug fixes: * Fixed keychain failures when HOME directory path contains spaces (e.g., `C:\Users\John Doe`). ([#188](https://github.com/danielrobbins/keychain/issues/188)) * Fixed username detection for usernames containing spaces (e.g., "Mathew Binkley" on Windows). Implemented portable `get_owner()` function using POSIX-defined `ls -ld` output format with intelligent field parsing to distinguish space-in-username from normal owner/group fields. * Fixed pidfile generation to properly quote `SSH_AUTH_SOCK` paths containing spaces while leaving `SSH_AGENT_PID` unquoted (numeric value). Rewrote `write_pidfile()` to use robust eval-in-subshell approach for extracting variable values from ssh-agent output. * All pidfile formats (sh/csh/fish) now correctly handle paths with spaces. * Fixed ssh-agent invocation to always use `-s` option for Bourne-compatible output, simplifying pidfile generation and improving compatibility across different environments. ([#185](https://github.com/danielrobbins/keychain/issues/185)) Testing and quality improvements: * Added `scripts/test-space-home.sh` - automated test harness that simulates HOME directories with spaces and validates proper handling. Returns proper exit codes for CI integration. * Integrated space-in-home test into GitHub Actions release workflow to prevent regressions. * Added ShellCheck disable comments with justification for intentional POSIX ls usage. * Fixed Unicode arrow characters in comments that caused ShellCheck errors. New features: * Added bash completion support (`completions/keychain.bash`) with intelligent context-aware completion for command-line options, SSH keys, GPG keys, and full `--extended` mode support. Based on work by @mikkoi with significant enhancements for keychain 2.9.x features: - Dynamically parses `keychain --help` for up-to-date option completion - Completes SSH key names from `~/.ssh/*.pub` files - Completes GPG key IDs (8-character short format) - `--extended` mode: `sshk:`, `gpgk:`, `host:` with prefix completion - Detects hostnames from `~/.ssh/config` for `host:` completion - ShellCheck compliant ([#186](https://github.com/danielrobbins/keychain/issues/186)) * Added Makefile targets: `install-completions` and `uninstall-completions` for optional bash completion installation (separate from default install target). * Updated RPM spec file (`keychain.spec.in`) for modern distributions: - Modernized description to focus on OpenSSH and GnuPG (removed obsolete ssh.com/Sun SSH) - Updated dependencies: `sh-utils` → `coreutils`, added `Recommends: bash-completion` - Added bash completion installation to RPM package Documentation: * Updated keychain.pod with detailed implementation notes for space handling, POSIX compliance, and the robust eval approach used in pidfile generation. * Standardized option ordering in keychain.pod to follow Unix convention (short option first, then long option), ensuring compatibility with bash completion regex patterns. * Added comprehensive COMPATIBILITY section to keychain.pod documenting: - Minimum OpenSSH version (7.3+) and supported features - GnuPG 2.1+ requirements for gpg-agent integration - Shell compatibility (Bourne/POSIX, csh/tcsh, fish) - **Git Bash (MSYS2) for Windows** - officially documented as supported platform - Legacy SSH implementation status (SunSSH, ssh.com) - Systemd user environment integration - Spaces in HOME and paths handling details * Updated README.md with bash completion installation instructions for both system-wide and user-only installations. ## keychain 2.9.6 (06 Sep 2025) Documentation/branding release (no functional code changes): * Updated references in wiki to reflect the new official home of Keychain at https://github.com/danielrobbins/keychain. * Consolidate historical references; retain only intentional archival note(s). Additional release engineering improvements: * Add release automation helpers: Makefile `release` (create) and `release-refresh` (asset replace), plus scripts under `scripts/` and GitHub Actions workflow to build artifacts on tag push (staging only). * Add `docs/release-steps.md` to formalize release process (numeric tags only, assets: tarball, wrapper script, man page). * Orchestrated release flow (`make release` / `make release-refresh`) now enforces: - Mandatory CI (Debian container) artifact fetch for the tag. - Normalized comparisons: * `keychain` – raw sha256. * `keychain.1` – raw sha256; on mismatch, re-compare with Pod::Man first line stripped. * Tarball – internal file list + per-file sha256 (man page internally normalized) ignoring tar/gzip metadata. - If (and only if) all artifacts match (raw or normalized) CI artifacts are used DIRECTLY for publication; local artifacts are never overwritten (kept for audit). - Any real content mismatch aborts unless `KEYCHAIN_FORCE_LOCAL=1` is explicitly set (single override; `KEYCHAIN_ADOPT_CI` removed). - Copy/paste diff command hints emitted on mismatch for rapid investigation. - Asset path indirection via exported variables prevents local file mutation, improving auditability. * Release notes body automatically extended with a Build Provenance table (sha256 for `keychain` and `keychain.1`) plus the tag commit SHA1. * Workflow continues to only stage artifacts; publication requires explicit maintainer action (no auto-release on tag push). ## keychain 2.9.5 (16 May 2025) This is a bugfix release. * Hardening checks were failing on Android and some MacOS environments. Make them more compatible and lower to warnings instead of aborting the script, until they have been tested in more environments. ([#177](https://github.com/funtoo/keychain/issues/177)) * Fixed issues with indentation of `note()`, `warn()`, `mesg()`. * Convert `SSH_AUTH_SOCK in pidfile is invalid; ignoring it` into a debug message, as this is normal when rebooting your system so is not really useful to show typically. ([#176](https://github.com/funtoo/keychain/issues/176)) ## keychain 2.9.4 (14 May 2025) This is a minor bugfix release. * Fix minor regression which allowed some warnings to display with `--quiet`. ([#175](https://github.com/funtoo/keychain/issues/175)) * "Cannot find separate public key" turned into a `note()` rather than `warn()`, along with several other non-critical notices. `note()` can be suppressed with `--quiet`, unlike `warn()`. ([#157](https://github.com/funtoo/keychain/issues/157)) * Minor improvement when wiping GnuPG keys with `--wipe` option so keychain output is more understandable when gpg-agent is not running. ## keychain 2.9.3 (14 May 2025) This is a security and bug fix release. Many thanks to those who have reported issues to GitHub, send in pull requests, and tested out fixes. 2.9.3 includes the following updates: * The `--quick` option logic had several bugs which have been resolved. Thanks to Filipe Fernandes (@ffernand) for reporting the issue and for assistance testing fixes. ([#167](https://github.com/funtoo/keychain/issues/167)) * Fix keychain `--query` exit code when no pidfile exists. ([#171](https://github.com/funtoo/keychain/issues/171)) * `--systemd` option should now be fixed. ([[#168](https://github.com/funtoo/keychain/issues/168)]) * Harden keychain so the use of the `--dir` and `--absolute` options cannot be used to instruct keychain to write pidfiles into insecure areas. ([#174](https://github.com/funtoo/keychain/issues/174)) Prior to this release, it was possible to use these options in combination with bad (empty) default umask to write pidfiles into a public area on disk where they were writable by other users. In the worst case, this could allow arbitrary execution of the contents of the malicious pidfile by keychain. This hardening now makes it difficult for a user to configure their keychain in a way that would allow this to happen. Note that if you are not using the `--dir` or `--absolute` options, keychain will use the `$HOME/.keychain` directory by default, which is typically under the full control of the current user and thus not exploitable. The hardening changes include: * Setting a global restrictive `umask` in the script. * Remove pidfiles before redirecting data to them to ensure they are created with restrictive permissions from the `umask`. * Check the keychain pidfile directory to ensure it is owned by the current user, and only the current user can access it (mode 700). If not, abort with an informative error message. * Check any existing pidfiles prior to use to make sure they are owned by the current user, and only the current user can access them. If not, abort with an informative error message. Thanks to Eisuke Kawashima (@e-kwsm) for reporting this issue, the `--systemd` issue, as well as for the `--query` fix. ## keychain 2.9.2 (2 May 2025) This is primarily a bug fix release, but also introduces the new `--extended` option -- see below: * Deprecate `--confhost` option and replace with `--extended` option. The old `--confhost myhost` would now be `--extended host:myhost`. This also allows specifying SSH keys (`sshk:` prefix), GPG keys ( `gpgk:` prefix) and hosts (`host:` prefix) together without confusion. * Well, I became intimately familiar with `IFS` the hard way. Fix 2.9.1 bug [#159](https://github.com/funtoo/keychain/issues/159) by reworking IFS settings and adding proper documentation to the right places. This fixes the `--timeout` option and also now allows `--stop` to work properly which was broken. * Improve `--agents` deprecation warning. * Have keychain properly adopt a currently-running gpg-agent providing ssh-agent functionality when `--ssh-use-gpg` is specified. * Explicitly clean up known-bad pidfiles during processing. * Deprecate `--confhost` option and replace with new `--extended` option. * Improve host-based key processing by using `ssh -G` to officially extract host-based keys. * Make `Makefile` BSD-compatible. ## keychain 2.9.1 (1 May 2025) This release fixes a major bug related to the `--eval` option with non-Bourne shells. * Fix `--eval` option so it works with non-Bourne shells ([#158](https://github.com/funtoo/keychain/issues/158)). * Last-minute option change: replace `--ssh-wipe` and `--gpg-wipe` with `--wipe [ssh|gpg|all]`. * Deprecate `--attempts` option which doesn't work with gpg-agent pinentry nor modern OpenSSH. * More script rewriting -- default to IFS of newline in the script, totally rework SSH and GPG key adding code. * Remove undocumented and likely unused `--` option. * Script is now at a svelte 1049 lines of code. ## keychain 2.9.0 (30 Apr 2025) These release notes contain a summary of all changes, including cumulative changes in pre-releases: * A new release after 8 years, with Daniel Robbins (script creator) returning as maintainer. * 60% of the script has been rewritten, and is now compliant with [ShellCheck](https://shellcheck.net). * `--agents` and `--inherit` options have been deprecated to improve ease-of-use. * `gpg-agent` no longer started by default -- only when a GPG key has been provided on the command-line. GnuPG 2.1+ supported. * GnuPG pidfiles with `-gpg` extension are deprecated and no longer used. * Better GnuPG integration: `gpg-agent` can be used for SSH key storage. This can be enabled by specifying one of the new `--ssh-allow-gpg` and `--ssh-spawn-gpg` options. Agent information for `gpg-agent`'s SSH socket will be stored in the regular pidfile for compatibility. * Add `--ssh-rm`, `--ssh-wipe`, `--gpg-wipe` options for removing/wiping SSH and GPG keys. This addresses GitHub Issue [#153](https://github.com/funtoo/keychain/issues/153). * `--clear` option is now designed to be used for "initial clearing" of keys only. * Many user interface output improvements, to provide additional detail. * `--debug` option which can be used to troubleshoot issues with keychain. * Manual page significantly improved: New section on invocation, as well as documentation of the startup and agent detection algorithm. * Addition of `--ssh-agent-socket` option to manually specify desired path of the ssh-agent socket when starting. * Addition of `--confallhosts` to load identity files for all hosts. * Various bug fixes and improvements. * Script size reduced from 1500 to 1133 lines. ## keychain 2.9.0_beta4 (26 Apr 2025) * Rewrite key parsing code to remove unwanted use of `wantagent gpg` in the code. This may fix previous bugs related to identifying and loading GPG keys. * Fix GitHub Issue [#61](https://github.com/funtoo/keychain/issues/61) by ensuring that any error messages generated when adding SSH or GPG keys are printed as warnings to facilitate troubleshooting by users. * Manually merge in fish shell examples into `keychain.pod`. * Resolve GitHub Issue [#75](https://github.com/funtoo/keychain/issues/75) and ensure that "IdentityFile" allows case variations. ## keychain 2.9.0_beta3 (25 Apr 2025) * The previous beta of keychain attempted to use gpg-agent by default instead of ssh-agent. This behavior has been changed so that now you must opt-in to using gpg-agent. There are two new options to allow you to do this: `--ssh-allow-gpg` and `--ssh-spawn-gpg`, which are documented in the man page. * Displayed information about found/started agents has been greatly enhanced. * New option `--debug`, which currently allows display of more information regarding keychain's decisions. * Full technical documentation for keychain's agent-detection algorithm in the man page. * Key decision points in keychain's internal code now have better comments. * Fixing behavior of `--noinherit` to match previous versions. * Fixing behavior of `--quick` to match previous versions. * Tweaking agent detection to match legacy `--inherit=local-once` option. * Many documentation updates and improvements. * Now at 1119 lines of code (from 1500 lines of code in keychain 2.8.5) ## keychain 2.9.0_beta2 (23 Apr 2025) * Code has been overhauled to be more maintainable and various parts of the codebase have been rewritten. During this process, which started with 2.9.0_alpha1, various things were broken. THIS IS THE FIRST POTENTIALLY VIABLE WORKING RELEASE since 2.8.5, so PLEASE TEST AND PROVIDE FEEDBACK. It will be marked as a non-prerelease on GitHub to get more active testing and feedback. * Please note -- this version of keychain uses gpg-agent by default, if available. We are evaluating the consequences of this change at the moment, request feedback on complications related to this change, and THIS DECISION MAY BE REVERSED in the final release of 2.9.0 (we may go back to defaulting to ssh-agent, and have an option to use gpg-agent in place of ssh-agent, instead of just automatically using gpg-agent.) Please provide feedback in GitHub issues based on your experience. We have already found some challenges with the gpg-agent-by-default strategy, so no need to convince anyone. Just share your feedback/opinion. * ChangeLog has been converted to ChangeLog.md (thanks @d4g33z) to facilitate better interoperability with GitHub and modern conventions. ## keychain 2.9.0_beta1 (15 Apr 2025) * Keychain will now detect when gpg-agent is available and has ssh-agent functionality, and use gpg-agent by default. To disable this behavior, use the '--nosub' option to disable auto-substitution of gpg-agent for ssh-agent. This implements GitHub issue #67 requested by Martin Väth. * Begin removal of support for gpg-agent earlier than 2.1. This release is about 9 years old at this point, and if you have a newer version of keychain on a system, it's likely you also have updated GNUPG. * Update of keychain project URL to point to GitHub, and minor copyright updates. ## keychain 2.9.0_alpha1 (9 Apr 2025) * Daniel Robbins returns as keychain maintainer. * 'keychain' and 'keychain.1' removed from git repo and added to .gitignore. * Make 'keychain.sh' fully-compliant with ShellCheck, and add necessary exception comments to the codebase. These changes require testing, as some of the suggested fixes are not desired, and I tried to catch all of these. Thus the \_alpha1 status. * Merge in typographical errors from Peter Pentchev (@ppentchev). (commit d8a566d6402a2e93ce1664cb9c7e9df3233323de) * Merge in validity checking for malformed SSH public key files, also from Peter Pentchev (@ppentchev). (commit 2722fdcc5d86725dd72995a83694862849494471) * Merge in support for --agents, which adds detection of gpg-agent which was disabled. From Mikko Koivunalho (@mikkoi). (commit 1d170da33be908c742a0840191533fe90b82db5b) * Merge in support for --agent-socket option, to specify the path for SSH_AUTH_SOCK manually. From Mikhail f. Shiryaev (@felixoid). (commit 2a3dfcd46e91c32a620d64035c42c8d2971c926f) * Fix handling of exit codes. This fix is from Manolis Androulidakis (@manolis-andr). (commit ced07855cca34664c382aaaf536e719113f9e4e4) * Add --confallhosts option to allow loading of keys from all hosts. This is from Ole Martin Ruud (@barskern). (commit 004107877d0258076653b291f25c1e6083fec0fb) * Update GPL-2 license file. This is from Karol Babioch (@ghost). (commit 15ad9e1c8d5624dd762e503c4da014bcc8ebe890) ## keychain 2.8.5 (24 Jan 2018) * Summary: Various fixes and support systemd gnupg sockets * Some shells don't support local builtin (Roy Marples) * Support systemd managed gnupg sockets (Pedro Romano) * Fix some lintian warnings in the man page (Chris West) * Fix issues loading pem keys (Jack Twilley) ## keychain 2.8.4 (19 Oct 2017) * Summary: Support to GPG2 (Ryan Harris) * Support busybox ps (Alastair Hughes) * Various optimizations ## keychain 2.8.3 (24 Jun 2016) * Summary: fix gpg key addition (Clemens Kaposi) ## keychain 2.8.2 (06 Nov 2015) * Summary: Support new ssh features, bug fix release. * Support for new hash algorithms (Ben Boeckel) * Remove bashisms (Daniel Hertz) * Various optimizations (Daniel Hahler) * --timeout option now gets passed to agent, doc fixes (Andrew Bezella, Emil Lundberg) * RPM, Makefile fixes (Mike Frysinger) ## keychain 2.8.1 (29 May 2015) * Summary: POSIX compatibility and bug fix release. * Only set PATH to a standard value if PATH is not set. Otherwise, do not modify. * Makefile Cygwin and RPM spec fixes (thanks Luke Bakken and Ricardo Silva) * Confhost fixes. Deprecate in_path. Use command -v instead. * Find_pids: Modify "ps" call to work with non-GNU ps. (Bryan Drewery) * Re-introduce POSIX compatibility (remove shopt.) (vaeth) ## keychain 2.8.0 (21 Mar 2015) * Support for OpenSSH 6.8 fingerprints. * Support for GnuPG 2.1.0. * Handle private keys that are symlinks, even if the associated public key is in the target directory rather than alongside the symlink. * Allow private keys to have extensions, such as foo.priv. When looking for matching public keys, look for foo.priv.pub, but also strip extension and look for foo.pub if foo.priv.pub doesn't exist. * Initial support for --list/-l option to list SSH keys. * Updated docs for fish shell usage. ## keychain 2.7.2_beta1 (07 July 2014) * Various changes and updates: * Fixes for fish from Marc Joliet. * Keychain will default to start only ssh-agent unless GPG is explicitly updated using --agents. * Write ~/.gpg-agent-info when launching gpg-agent - fix from Thomas Spura. * Add support for injecting agents into systemd (Ben Boeckel) * Add support for --query option (Ben Boeckel) * Add --absolute flag, allowing user to set a full path without getting a .keychain suffix automatically appended. * Add --confhost option to scan ~/.ssh/config file to locate private key path specified there. ## keychain 2.7.1 (07 May 2010) * 07 May 2010; Daniel Robbins : Addition of a "make clean" target. removal of runtests as it is currently broken. * 07 May 2010; Daniel Robbins : New release process in Makefile and release.sh - keychain release tarball will now contain pre-generated keychain, keychain.1 and keychain.spec so that users do not need to run "make". Updated README.rst to refer to the "source code" as a "release archive" since it contains both source code and ready-to-go script and man page. * 14 Apr 2010; Daniel Robbins : GPG fix from Gentoo bug 203871; from Frederic Bathelery. This fix will fix the issue with pinentry starting in the background and not showing up in the terminal. * 20 Feb 2010; Daniel Robbins : MacOS X documentation fix from James Turnbull. ## keychain 2.7.0 (23 Oct 2009) * 23 Oct 2009; Daniel Robbins : updated README.rst with 2.7.0 and MacOS X package update. * 18 Oct 2009; Daniel Robbins : lockfile() replacement from Parallels Inc. OpenVZ code, takelock() rewrite, resulting in ~100 line code savings. Default lock timeout set to 5 seconds, and now keychain will try to forcefully acquire the lock if the timeout aborts, rather than simply failing and aborting. * 30 Sep 2009; Daniel Robbins : MacOS X/BSD improvements: fix sed call in Makefile for MacOS X and presumably other *BSD environments, Rename COPYING to COPYING.txt, slight COPYING.txt formatting changes to allow license to display more cleanly from MacOS X .pkg automated install. Fixed POD errors (removed '=end'). * 29 Sep 2009; Daniel Robbins : disable "Identity added" messages when --quiet is specified (Gentoo bug #250328, thanks to Richard Laager,) --help will print output to stdout (Gentoo bug #196060, thanks to Elan Ruusamäe,) output cleanup and colorization changes - moving away from blue and over to cyan as it displays better terminals with black background. Also some additional colorization. Version bump to 2.6.10. ## keychain 2.6.9 (26 Jul 2009) * 26 Jul 2009; Daniel Robbins : Close Gentoo bug 222953 from Bernd Petrovitsch to fix potential issues with GNU grep, Mac OS X color fix when called with --eval from Aron Griffis , Perl 5.10 Makefile fix from Aron Griffis . Transition README to README.rst (reStructuredText). Updated maintainership information. Simplified default output ( --version or --help now required to show version, copyright and license information.) ## keychain 2.6.8 (24 Oct 2006) * 24 Oct 2006; Aron Griffis : Save `LC_ALL` for gpg invocation so that pinentry-curses works. This affected peper and kloeri, though it seems to work for me in any case. ## keychain 2.6.7 (24 Oct 2006) * 24 Oct 2006; Aron Griffis : Prevent `gpg_listmissing` from accidentally loading keys ## keychain 2.6.6 (08 Sep 2006) * 08 Sep 2006; Aron Griffis : Make --lockwait -1 mean forever. Previously 0 meant forever but was undocumented. Add more locking regression tests #137981 ## keychain 2.6.5 (08 Sep 2006) * 08 Sep 2006; Aron Griffis : Break out of loop when empty lockfile can't be removed #127471. Add locking regression tests: `100_lock_stale` `101_lock_held` `102_lock_empty` `103_lock_empty_cant_remove` ## keychain 2.6.4 (08 Sep 2006) * 08 Sep 2006; Aron Griffis : Add validinherit function so that validity of `SSH_AUTH_SOCK` and friends can be validated from startagent rather than up front. The advantage is that warning messages aren't emitted unnecessarily when `--inherit *-once`. Fix `--eval` for fish, and add new testcases: * `053_start_with_--eval_ksh` * `054_start_with_--eval_fish` * `055_start_with_--eval_csh` ## keychain 2.6.3 (07 Sep 2006) * 07 Sep 2006; Aron Griffis : Support fish: http://roo.no-ip.org/fish/ Thanks to Ilkka Poutanen for the patch. ## keychain 2.6.2 (20 Mar 2006) * 20 Mar 2006; Aron Griffis : Add `--confirm` option and corresponding regression tests for Debian bug 296382. Thanks to Liyang HU for the patch. Also add initialization for `$ssh_timeout` which was being inherited from the environment and add regression tests for `--timeout` ## keychain 2.6.1 (10 Oct 2005) * 10 Oct 2005; Aron Griffis : Change `unset evalopt` to `evalopt=false` and run through *all* the regression tests instead of just the new ones. *sigh* ## keychain 2.6.0 (10 Oct 2005) * 10 Oct 2005; Aron Griffis : Add the `--eval` option which makes keychain startup easier. See the man-page for examples. Get rid of the release notes from README, so now this file is where changes are tracked. ## keychain 2.5.5 (28 Jul 2005) * 28 Jul 2005; Aron Griffis : Add the `--env` option and automatic reading of `.keychain/env`. This allows variables such as PATH to be overridden for peculiar environments ## keychain 2.5.4.1 (11 May 2005) * 11 May 2005; Aron Griffis : A minor bug in 2.5.4 resulted in always exiting with non-zero status. Change back to the correct behavior of zero for success, non-zero for failure ## keychain 2.5.4 (11 May 2005) * 11 May 2005; Aron Griffis : Fix bug 92316: If any locale variables are set, override them with `LC_ALL=C`. This fixes a multibyte issue with awk that could keep a running ssh-agent from being found. Fix bug 87340: Use files instead of symlinks for locking, since symlink creation is not atomic on cygwin. ## keychain 2.5.3.1 (10 Mar 2005) * 10 Mar 2005; Aron Griffis : Fix problem introduced in 2.5.3 wrt adding gpg keys to the agent. Thanks to Azarah for spotting it. ## keychain 2.5.3 (09 Mar 2005) * 09 Mar 2005; Aron Griffis : Improve handling of DISPLAY by unsetting if blank. Call gpg with `--use-agent` explicitly. ## keychain 2.5.2 (06 Mar 2005) * 06 Mar 2005; Aron Griffis : Fix bug 78974 "keychain errors on Big/IP (x86 BSD variant)" by refraining from using ! in conditional expressions. Fix RSA fingerprint extraction on Solaris, reported in email by Travis Fitch. Use \$HOSTNAME when possible instead of calling `uname -n` to improve bash_profile compatibility. ## keychain 2.5.1 (12 Jan 2005) * 12 Jan 2005; Aron Griffis : Don't accidentally inherit a forwarded agent when inheritwhich=local-once. Move the --stop warning after the version splash. ## keychain 2.5.0 (07 Jan 2005) * 07 Jan 2005; Aron Griffis : Add inheritance support via --inherit. Add parameters to --stop for more control. Change the default behavior of keychain to inherit if there's no keychain agent running ("--inherit local-once"), and refrain from killing other agents unless "--stop others" is specified. ## keychain 2.4.3 (17 Nov 2004) * 17 Nov 2004; Aron Griffis : Fix bug 69879: Update findpids to work again on BSD; it has been broken since the changes in version 2.4.2. Now we use OSTYPE (bash) or uname to determine the system type and call ps appropriately. ## keychain 2.4.2.1 (30 Sep 2004) * 30 Sep 2004; Aron Griffis : Fix minor issues in the test for existing gpg keys wrt DISPLAY ## keychain 2.4.2 (29 Sep 2004) * 29 Sep 2004; Aron Griffis : Make gpg support more complete. Allow adding keys, clearing the agent, etc. Fix --quick support to work properly again; it was broken since 2.4.0. Change default --attempts to 1 since the progs ask multiple times anyway. ## keychain 2.4.1 (22 Sep 2004) * 22 Sep 2004; Aron Griffis : Fix bugs 64174 and 64178; support Sun SSH, which is really OpenSSH in disguise and a few critical outputs changed. Thanks to Nathan Bardsley for lots of help debugging on Solaris 9 * 15 Sep 2004; Aron Griffis : Fix pod2man output so it formats properly on SGI systems. Thanks to Matthew Moore for reporting the problem. ## keychain 2.4.0 (09 Sep 2004) * 09 Sep 2004; Aron Griffis : Fix bug 26970 with first pass at gpg-agent support * Fix Debian bug 269722; don't filter output of ssh-add * Fix bug reported by Marko Myllynen regarding keychain and Solaris awk's inability to process -F'[ :]' * Fix bug in now_seconds calculation, noticed by me. ## keychain 2.3.5 (28 Jul 2004) * 28 Jul 2004; Aron Griffis : Fix bug 58623 with patch from Daniel Westermann-Clark; don't put an extra newline in the output of listmissing * Generate keychain.spec from keychain.spec.in automatically so that the version can be set appropriately. ## keychain 2.3.4 (24 Jul 2004) * 24 Jul 2004; Aron Griffis : Fix bug 28599 reported by Bruno Pelaia; ignore defunct processes in ps output ## keychain 2.3.3 (30 Jun 2004) * 30 Jun 2004; Aron Griffis : Fix bug reported by Matthew S. Moore in email; escape the backticks in --help output * Fix bug reported by Herbie Ong in email; set pidf, cshpidf and lockf variables after parsing command-line to honor --dir setting * Fix bug reported by Stephan Stahl in email; make spaces in filenames work throughout keychain, even in pure Bourne shell * Fix operation on HP-UX with older OpenSSH by interpreting output of ssh-add as well as the error status ## keychain 2.3.2 (16 Jun 2004) * 16 Jun 2004; Aron Griffis : Fix bug 53837 (keychain needs ssh-askpass) by unsetting SSH_ASKPASS when --nogui is specified ## keychain 2.3.1 (03 Jun 2004) * 03 Jun 2004; Aron Griffis : Fix bug 52874: problems when the user is running csh ## keychain 2.3.0 (14 May 2004) * 14 May 2004; Aron Griffis : Rewrite the locking code to avoid procmail ## keychain 2.2.2 (03 May 2004) * 03 May 2004; Aron Griffis : Call loadagent prior to generating `$HOSTNAME-csh` file so that variables are set. ## keychain 2.2.1 (27 Apr 2004) * 27 Apr 2004; Aron Griffis : Find running ssh-agent processes by searching for /[s]sh-agen/ instead of /[s]sh-agent/ for the sake of Solaris, which cuts off ps -u output at 8 characters. Thanks to Clay England for reporting the problem and testing the fix. ## keychain 2.2.0 (21 Apr 2004) * 21 Apr 2004; Aron Griffis : Rewrote most of the code, organized into functions, fixed speed issues involving ps, fixed compatibility issues for various UNIXes, hopefully didn't introduce too many bugs. This version has a --quick option (for me) and a --timeout option (for carpaski). * Also added a Makefile and converted the man-page to pod for easier editing. See perlpod(1) for information on the format. Note that the pod is sucked into keychain and colorized when you run make. ## keychain 2.0.3 (06 Apr 2003) * 06 Apr 2003; Seth Chandler : Added keychain man page, fixed bugs with displaying colors for keychain --help. Also added a \$grepopts to fix the grepping for a pid on cygwin Also added a TODO document color fix based on submission by Luke Holden ## keychain 2.0.2 (26 Aug 2002) * 26 Aug 2002; the Tru64 fix didn't work; it was being caused by "trap - foo" rather than "tail +2 -". Now really fixed. * 26 Aug 2002; fixed "ssh-add" call to only redirect stdin (thus enabling ssh-askpass) if ssh_askpass happens to be set; this is to work around a bug in openssh were redirecting stdin will enable ssh-askpass even if ssh_askpass isn't set, which contradicts the openssh 3.4_p1 man page. to enable ssh-askpass, keychain now requires that the ssh_askpass var be set to point to your askpass program. ## keychain 2.0.1 (24 Aug 2002) * 24 Aug 2002; "--help" fixes; the keychain files were listed as sh-\${HOSTNAME} rather than \${HOSTNAME}-sh. Now consistent with the actual program. Thanks to Christian Plessl , others for reporting this issue. * 24 Aug 2002; cycloon : "If you add < /dev/null when adding the missingkeys via "ssh-add \${missingkeys}" (at line 454 of version 2.0) so that it reads: "ssh-add \${missingkeys} < /dev/null" then users can use program like x11-ssh-askpass in xfree to type in their passphrase. It then still works for users on shell, depending if \$DISPLAY is set." Added. * 24 Aug 2002; A fix to calling "tail" that *should* fix things for Tru64 Unix; unfortunately, I have no way to test but the solution should be portable to all other flavors of systems. Thanks to Mark Scarborough for reporting the issue. * 24 Aug 2002; Changed around the psopts detection stuff so that "-x -u \$me f" is used; this is needed on MacOS X. Thanks to Brian Bergstrand , others for reporting this issue. ## keychain 2.0 (17 Aug 2002) * 17 Aug 2002; (Many submitters): A fix for keychain when running on HP-UX 10.20. * 17 Aug 2002; Patrice DUMAS - DOCT : Now perform help early on to avoid unnecessary processing. Also added --dir option to allow keychain to look in an alternate location for the .keychain directory (use like this: "keychain --dir /var/foo") * 17 Aug 2002; Martial MICHEL : Martial also suggested moving help processing to earlier in the script. He also submitted a patch to place .ssh-agent-* files in a ~/.keychain/ directory, which makes sense particularly for NFS users so I integrated the concept into the code. * 17 Aug 2002; Fred Carter : Cygwin fix to use proper "ps" options. * 17 Aug 2002; Adrian Howard : patch so that lockfile gets removed even if --noask is specified. * 17 Aug 2002; Mario Wolff : Replaced an awk dependency with a shell construct for improved performance. * 17 Aug 2002; Marcus Stoegbauer , Dmitry Frolov : I (Daniel Robbins) solved problems reported by Marcus and Dmitry (mis-parsed command line issues) by following Dmitry's good suggestion of performing argument parsing all at once at the top of the script. * 17 Aug 2002; Brian W. Curry : Added commercial SSH2 client support; improved output readability by initializing myfail=0; integrated Cygwin support into the main keychain script; improved Cygwin support by setting "trap" appropriately. Thanks Brian! ## keychain 1.9 (04 Mar 2002) * 04 Mar 2002; changed license from "GPL, v2 or later" to "GPL v2". * 04 Mar 2002; added "keychain.cygwin" for Cygwin systems. It may be time to follow this pattern and start building separate, optimized scripts for each platform so they don't get too sluggish. Maybe I could use a C preprocessor for this. * 06 Dec 2001; several people: Solaris doesn't like '-e' comparisons; switched to '-f' ## keychain 1.8 (29 Nov 2001) * 29 Nov 2001; Philip Hallstrom (philip@adhesivemedia.com) Added a "--local" option for removing the \${HOSTNAME} from the various files that keychain creates. Handy for non-NFS users. * 29 Nov 2001; Aron Griffis (agriffis@gentoo.org) Using the Bourne shell "type" builtin rather than using the external "which" command. Should make things a lot more robust and slightly faster. * 09 Nov 2001; Mike Briseno (mike@radik.com) Solaris' "which" command outputs "no lockfile in..." to stdout rather than stderr. A one-line fix (test the error condition) has been applied. * 09 Nov 2001; lockfile settings tweak * 09 Nov 2001; Rewrote how keychain detects failed passphrase attempts. If you stop making progress providing valid passphrases, it's three strikes and you're out. * 09 Nov 2001; Constantine P. Sapuntzakis (csapuntz@stanford.edu) Some private keys can't be "ssh-keygen -l -f"'d; this patch causes keychain to look for the corresponding public key if the private key doesn't work. Thanks Constantine! * 09 Nov 2001; Victor Leitman (vleitman@yahoo.com) CYAN color misdefined; fixed. * 27 Oct 2001; Brian Wellington (bwelling@xbill.org) A "quiet mode" (--quiet) fix; I missed an "echo". * 27 Oct 2001; J.A. Neitzel (jan@belvento.org) Missed another "kill -9"; it's now gone. ## keychain 1.7 (21 Oct 2001) * 21 Oct 2001; Frederic Gobry (frederic.gobry@smartdata.ch) Frederic suggested using procmail's lockfile to serialize the execution of critical parts of keychain, thus avoiding multiple ssh-agent processes being started if you happen to have multiple xterms open automatically when you log in. Initially, I didn't think I could add this, since systems may not have the lockfile command; however, keychain will now auto-detect whether lockfile is installed; if it is, keychain will automatically use it, thus preventing multiple ssh-agent processes from being spawned. * 21 Oct 2001; Raymond Wu (ursus@usa.net): --nocolor test is no longer inside the test for whether "echo -e" works. According to Raymond, this works optimally on his Solaris box. * 21 Oct 2001; J.A. Neitzel (jan@belvento.org): No longer "kill -9" our ssh-agent processes. SIGTERM should be sufficient and will allow ssh-agent to clean up after itself (this reverses a previously-applied patch). * 21 Oct 2001; Thomas Finneid (tfinneid@online.no): Added argument "--quiet | -q" to make the program less intrusive to the user; with it, only error and interactive messages will appear. * 21 Oct 2001; Thomas Finneid (tfinneid@online.no): Changed the format of some arguments to bring them more in line with common *nix programs: added "-h" as alias for "--help"; added "-k" as alias for "--stop" * 21 Oct 2001; Mark Stosberg (mark@summersault.com): \$pidf to "\$pidf" fixes to allow keychain to work with paths that include spaces (for Darwin and MacOS X in particular). * 21 Oct 2001; Jonathan Wakely (redi@redi.uklinux.net): Small patch to convert "echo -n -e" to "echo -e "\c"" for FreeBSD compatibility. ## keychain 1.6 (15 Oct 2001) * 13 Oct 2001; Ralf Horstmann (ralf.horstmann@webwasher.com): Add /usr/ucb to path for Solaris systems. * 11 Oct 2001; Idea from Joe Reid (jreid@vnet.net): Try to add multiple keys using ssh-add; avoid typing in identical passphrases more than once. Good idea! ## keychain 1.5 (21 Sep 2001) * 21 Sep 2001; David Hull (hull@paracel.com): misc. compatibility, signal handling, cleanup fixes * 21 Sep 2001; "ps" test to find the right one for your OS. * 20 Sep 2001; Marko Myllynen (myllynen@lut.fi): "grep [s]sh-agent" to "grep [s]sh-agent" (zsh fix) ## keychain 1.4 (20 Sep 2001) * 20 Sep 2001; David Hull (hull@paracel.com): "touch \$foo" to ">\$foo" optimization and other "don't fork" fixes. Converted \${foo#--} to a case statement for Solaris sh compatibility. * 20 Sep 2001; Try an alternate "ps" syntax if our default one fails. This should give us Solaris and IRIX (sysV) compatibility without breaking BSD. * 20 Sep 2001; Hans Peter Verne (h.p.verne@usit.uio.no); "echo -e" to "echo \$E" (for IRIX compatibility with --nocolor), optimization of grep ("grep [s]sh-agent") * 17 Sep 2001; Marko Myllynen (myllynen@lut.fi): Various fixes: trap signal 2 if signal INT not supported (NetBSD); handle invalid keys correctly; ancient version of ash didn't support ~, so using \$HOME; correct zsh instruction; minor cleanups ## keychain 1.3 (12 Sep 2001) * 12 Sep 2001; Minor color changes; the cyan was hard to read on xterm-colored terms so it was switched to bold. Additional --help text added. * 10 Sep 2001; We now use .ssh-agent-[hostname] instead of .ssh-agent. We now create a .ssh-agent-csh-[hostname] file that can be sourced by csh-compatible shells. We also now kill all our existing ssh-agent processes before starting a new one. * 10 Sep 2001; Robert R. Wal (rrw@hell.pl): Very nice NFS fixes, colorization fixes, tcsh redirect -> grep -v fix. Thanks go out to others who sent me similar patches. * 10 Sep 2001; Johann Visagie (johann@egenetics.com): "source" to "." shell-compatibility fixes. Thanks for the FreeBSD port. * 10 Sep 2001; Marko Myllynen (myllynen@lut.fi): rm -f \$pidf after stopping ssh-agent fix ## keychain 1.2 (09 Sep 2001) * 09 Sep 2001; README updates to reflect new changes. * 09 Sep 2001; Marko Myllynen (myllynen@lut.fi): bash 1/zsh/sh compatibility; now only tries to kill *your* ssh-agent processes, version fix, .ssh-agent file creation error detection. Thanks! ## keychain 1.1 (07 Sep 2001) * 07 Sep 2001; Addition of README stating that keychain requires bash 2.0 or greater, as well as quick install directions and web URL. * 07 Sep 2001; Explicitly added /sbin and /usr/sbin to path, and then called "pidof". I think that this is a bit more robust. * 06 Sep 2001; from John Ellson (ellson@lucent.com): "pidof" changed to "/sbin/pidof", since it's probably not in \$PATH * 06 Sep 2001; New ChangeLog! :) ## keychain 1.0 (Aug 2001) * initial release keychain-2.9.8/MAINTAINERS.txt000066400000000000000000000006541510172470100156430ustar00rootroot00000000000000Originally authored by Daniel Robbins Maintained August 2002 - April 2003 by Seth Chandler Maintained and rewritten April 2004 - July 2007 by Aron Griffis Maintained July 2009 - Sept 2017 by Daniel Robbins Maintained September 2017 - 2018 by Ryan Harris Maintained currently by Daniel Robbins keychain-2.9.8/Makefile000066400000000000000000000060331510172470100147650ustar00rootroot00000000000000# For BSD, AIX, Solaris: V:sh = cat VERSION D:sh = date +'%d %b %Y' Y:sh = date +'%Y' # for GNU Make: V ?= $(shell cat VERSION) D ?= $(shell date +'%d %b %Y') Y ?= $(shell date +'%Y') PREFIX ?= /usr/local COMPLETIONSDIR ?= $(PREFIX)/share/bash-completion/completions all: keychain.1 keychain keychain.spec .PHONY : tmpclean tmpclean: rm -rf dist keychain.1.orig keychain.txt .PHONY : clean clean: tmpclean rm -rf keychain.1 keychain keychain.spec keychain.spec: keychain.spec.in keychain.sh VERSION sed 's/KEYCHAIN_VERSION/$V/' keychain.spec.in > keychain.spec keychain.1: keychain.pod keychain.sh VERSION pod2man --name=keychain --release=$V \ --center='https://github.com/danielrobbins/keychain' \ keychain.pod keychain.1 sed -i.orig -e "s/^'br /.br /" keychain.1 keychain.1.gz: keychain.1 gzip -9 keychain.1 GENKEYCHAINPL = open P, "keychain.txt" or die "cannot open keychain.txt"; \ while (

) { \ $$printing = 0 if /^\w/; \ $$printing = 1 if /^(SYNOPSIS|OPTIONS)/; \ $$printing || next; \ s/\$$/\\\$$/g; \ s/\`/\\\`/g; \ s/\\$$/\\\\/g; \ s/\*(\w+)\*/\$${CYAN}$$1\$${OFF}/g; \ s/(^|\s)(-+[-\w]+)/$$1\$${GREEN}$$2\$${OFF}/g; \ $$pod .= $$_; \ }; \ open B, "keychain.sh" or die "cannot open keychain.sh"; \ $$/ = undef; \ $$_ = ; \ s/INSERT_POD_OUTPUT_HERE[\r\n]/$$pod/ || die; \ s/\#\#VERSION\#\#/$V/g || die; \ print keychain: keychain.sh keychain.txt VERSION MAINTAINERS.txt perl -e '$(GENKEYCHAINPL)' | sed -e 's/##CUR_YEAR##/$(Y)/g' >keychain || rm -f keychain chmod +x keychain keychain.txt: keychain.pod pod2text keychain.pod keychain.txt dist/keychain-$V.tar.gz: keychain keychain.1 keychain.spec mkdir -p dist rm -rf dist/keychain-$V git archive --format=tar --prefix=keychain-$V/ HEAD | tar -xf - -C dist/ cp keychain keychain.1 keychain.spec dist/keychain-$V/ tar -C dist -czf dist/keychain-$V.tar.gz keychain-$V rm -rf dist/keychain-$V ls -l dist/keychain-$V.tar.gz # --- Release Automation Helpers --- .PHONY: release release-refresh RELEASE_ASSETS=dist/keychain-$V.tar.gz keychain keychain.1 # "release" will orchestrate a tagged release with CI artifact validation & confirmation. release: clean $(RELEASE_ASSETS) @echo "Orchestrating release $(V)"; \ if [ -z "$$GITHUB_TOKEN" ]; then \ echo "GITHUB_TOKEN not set; export a repo-scoped token to proceed." >&2; exit 1; \ fi; \ ./scripts/release-orchestrate.sh create $(V) # "release-refresh" updates assets of an existing GitHub release (e.g. fixups) with CI validation. release-refresh: clean $(RELEASE_ASSETS) @echo "Orchestrating release-refresh $(V)"; \ if [ -z "$$GITHUB_TOKEN" ]; then \ echo "GITHUB_TOKEN not set; export a repo-scoped token to proceed." >&2; exit 1; \ fi; \ ./scripts/release-orchestrate.sh refresh $(V) # --- Bash Completion --- .PHONY: install-completions uninstall-completions install-completions: install -d -m 0755 $(DESTDIR)$(COMPLETIONSDIR) install -m 0644 completions/keychain.bash $(DESTDIR)$(COMPLETIONSDIR)/keychain uninstall-completions: rm -f $(DESTDIR)$(COMPLETIONSDIR)/keychain keychain-2.9.8/README.md000066400000000000000000000072221510172470100146050ustar00rootroot00000000000000 Introduction to Keychain ======================== `Keychain` helps you to manage SSH and GPG keys in a convenient and secure manner. It acts as a frontend to `ssh-agent` and `ssh-add`, but allows you to easily have one long running `ssh-agent` process per system, rather than the norm of one `ssh-agent` per login session. This dramatically reduces the number of times you need to enter your passphrase. With `keychain`, you only need to enter a passphrase once every time your local machine is rebooted. `Keychain` also makes it easy for remote cron jobs to securely "hook in" to a long running `ssh-agent` process, allowing your scripts to take advantage of key-based logins. `Keychain` also integrates with `gpg-agent`, so that GPG keys can be cached at the same time as SSH keys. Bash Completion =============== Keychain includes bash completion support for command-line options, SSH keys, GPG keys, and the `--extended` key format (`sshk:`, `gpgk:`, `host:`). Most Linux distributions will install the completion script automatically when you install keychain via your package manager. For manual installation: - **System-wide** (requires `bash-completion` package and root access): ``` sudo make install-completions ``` This installs to `/usr/local/share/bash-completion/completions/` by default. Use `PREFIX=/usr` for `/usr/share/bash-completion/completions/`. - **User-only** (no root required): ``` mkdir -p ~/.local/share/bash-completion/completions cp completions/keychain.bash ~/.local/share/bash-completion/completions/keychain ``` After installation, restart your shell or run: ``` source /etc/bash_completion ``` **Tip:** If pressing tab doesn't show all possible completions when there are multiple matches (e.g., `sshk:id_` completes to common prefix but doesn't list all keys), add this to your `~/.inputrc`: ``` set show-all-if-ambiguous on ``` Then restart your shell or run `bind -f ~/.inputrc`. Support This Project ==================== Keychain is maintained by [BreezyOps](https://breezyops.com) - Daniel Robbins' Open Source Innovation Lab. If you find it useful, please consider: - Starring the repository ⭐ - Joining [Discussions](https://github.com/danielrobbins/keychain/discussions) to share tips and ask questions 💬 - [Supporting development](https://paypal.me/breezyops) to help maintain and improve keychain! ❤️ Your support helps keep this project alive and actively maintained, and supports the creation of future projects. Thank you! IMPORTANT - GitHub Contributors =============================== Please submit pull requests against the `master` branch which should track official releases. Before submitting your PR, please: 1. Make sure that you have [ShellCheck](https://shellcheck.net) enabled in your IDE and that your changes don't introduce any bashisms or other non-POSIX things. For any *intended* exceptions, such as non-quoting of expanded variables, please insert a commented ShellCheck exception to disable the warning, and if not totally obvious, then add a comment to the exception like this: # shellcheck disable=SC2086 # this is intentional: If you do not understand a ShellCheck warning, then don't just blindly disable it. Do some research first, make any necessary changes, and then submit your PR. 2. Please use tabs for initial indentation, not spaces. 3. Don't use tabs at the end of lines, such as to align comments. Either use a full line to add a comment or add a short comment at the end of a command, separating the "#" from the actual command with just a single space. 4. For any new features or options, update `keychain.pod` with documentation on how to use the new feature. keychain-2.9.8/VERSION000066400000000000000000000000061510172470100143670ustar00rootroot000000000000002.9.8 keychain-2.9.8/completions/000077500000000000000000000000001510172470100156575ustar00rootroot00000000000000keychain-2.9.8/completions/keychain.bash000066400000000000000000000155051510172470100203170ustar00rootroot00000000000000#!/usr/bin/env bash # Bash completion for keychain # https://github.com/danielrobbins/keychain # # Original script by Mikko Koivunalho (@mikkoi) # https://github.com/mikkoi/keychain-bash-completion # Enhanced with --extended mode support by Daniel Robbins __keychain_init_completion() { COMPREPLY=() _get_comp_words_by_ref cur prev words cword } # Get ssh key file names. Find all files with .pub suffix and remove the suffix __keychain_ssh_keys() { if [ -d ~/.ssh ]; then keys=() while IFS= read -r -d '' file; do key="$(basename -s .pub "$file")" keys+=( "$key" ) done < <(\find "${HOME}/.ssh" -type f -name \*.pub -print0) echo "${keys[*]}" fi } # Get gpg keys - 8-character short key IDs __keychain_gpg_keys() { keys=() while IFS= read -r row; do if [[ "$row" =~ ^sec:[a-z]:[[:digit:]]{0,}:[[:alnum:]]{0,}:([[:alnum:]]{0,}): ]]; then key="$(echo "${BASH_REMATCH[1]}" | cut -b 9-16)" keys+=( "$key" ) fi done < <(\gpg --list-secret-keys --with-colons 2>/dev/null) echo "${keys[*]}" } # Get hostnames from ~/.ssh/config __keychain_ssh_config_hosts() { if [ -f ~/.ssh/config ]; then # Extract Host entries, excluding wildcards grep -i "^Host " ~/.ssh/config 2>/dev/null | \ awk '{print $2}' | \ grep -v '[*?]' fi } # Parse command-line options from keychain --help output __keychain_command_line_options() { opts=() # Try to find keychain executable (handle Git Bash/MINGW64 PATH issues) local keychain_cmd if command -v keychain >/dev/null 2>&1; then keychain_cmd="keychain" elif [ -x ./keychain ]; then keychain_cmd="./keychain" elif [ -x ./keychain.sh ]; then keychain_cmd="./keychain.sh" else # Fallback: provide common options if keychain not found echo "-h --help -V --version -q --quiet -Q --quick" return 0 fi while IFS= read -r row; do # Match: " -X --option" format (short first, long second) if [[ "$row" =~ ^[[:space:]]{4}([-]{1}[[:alpha:]]{1})[[:space:]]{1,}([-]{2}[[:alnum:]-]{1,})[[:space:]]{0,} ]] then opt1="${BASH_REMATCH[1]}" opts+=( "$opt1" ) opt2="${BASH_REMATCH[2]}" opts+=( "$opt2" ) # Match: " --option" format (long only) elif [[ "$row" =~ ^[[:space:]]{4}([-]{2}[[:alnum:]-]{1,})[[:space:]]{1,} ]] then opt="${BASH_REMATCH[1]}" opts+=( "$opt" ) fi done < <("$keychain_cmd" --help 2>/dev/null) echo "${opts[*]}" } _keychain() { local cur if declare -F _init_completion >/dev/null 2>&1; then _init_completion -n : || return else # Fallback if bash-completion not available COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" fi # Check if --extended is in the command line local extended_mode=false for word in "${COMP_WORDS[@]}"; do [[ "$word" == "--extended" ]] && extended_mode=true && break done # Handle --extended mode completions with prefixes if [[ "$extended_mode" == true ]]; then local prefix completions=() # Determine which prefix type we're completing case "$cur" in sshk:*) # shellcheck disable=SC2207 local items=( $(__keychain_ssh_keys) ) prefix="sshk:" ;; gpgk:*) # shellcheck disable=SC2207 local items=( $(__keychain_gpg_keys) ) prefix="gpgk:" ;; host:*) # shellcheck disable=SC2207 local items=( $(__keychain_ssh_config_hosts) ) prefix="host:" ;; s*|g*|h*) # Handle partial prefix matches (s->sshk:, g->gpgk:, h->host:) # Only if no colon present yet [[ ! "$cur" =~ : ]] || return 0 [[ "sshk:" == "$cur"* ]] && completions+=( "sshk:" ) [[ "gpgk:" == "$cur"* ]] && completions+=( "gpgk:" ) [[ "host:" == "$cur"* ]] && completions+=( "host:" ) # If we have prefix matches, show them if [ ${#completions[@]} -gt 0 ]; then # shellcheck disable=SC2207 COMPREPLY=( $(compgen -W "${completions[*]}" -- "$cur") ) compopt -o nospace 2>/dev/null fi # Also add matching options # shellcheck disable=SC2207 local opts=( $(__keychain_command_line_options) ) if [ ${#opts[@]} -gt 0 ]; then # shellcheck disable=SC2207 COMPREPLY+=( $(compgen -W "${opts[*]}" -- "$cur") ) fi return 0 ;; -*) # Show options only # shellcheck disable=SC2207 local opts=( $(__keychain_command_line_options) ) if [ ${#opts[@]} -gt 0 ]; then # shellcheck disable=SC2207 COMPREPLY=( $(compgen -W "${opts[*]}" -- "$cur") ) fi return 0 ;; *) # No prefix yet, offer all prefixed possibilities # shellcheck disable=SC2207 local ssh_keys=( $(__keychain_ssh_keys) ) # shellcheck disable=SC2207 local gpg_keys=( $(__keychain_gpg_keys) ) # shellcheck disable=SC2207 local hosts=( $(__keychain_ssh_config_hosts) ) for key in "${ssh_keys[@]}"; do completions+=( "sshk:$key" ); done for key in "${gpg_keys[@]}"; do completions+=( "gpgk:$key" ); done for host in "${hosts[@]}"; do completions+=( "host:$host" ); done # shellcheck disable=SC2207 COMPREPLY=( $(compgen -W "${completions[*]}" -- "$cur") ) __ltrim_colon_completions "$cur" 2>/dev/null || true compopt -o nospace 2>/dev/null return 0 ;; esac # Build completions for sshk:/gpgk:/host: prefixes if [[ -n "$prefix" ]]; then for item in "${items[@]}"; do completions+=( "${prefix}${item}" ) done # shellcheck disable=SC2207 COMPREPLY=( $(compgen -W "${completions[*]}" -- "$cur") ) __ltrim_colon_completions "$cur" 2>/dev/null || true compopt -o nospace 2>/dev/null return 0 fi fi # Normal mode (no --extended): complete bare keys and options # shellcheck disable=SC2207 COMPREPLY=( $(compgen -W "$(__keychain_command_line_options) $(__keychain_ssh_keys) $(__keychain_gpg_keys)" -- "$cur") ) return 0 } complete -F _keychain keychain keychain-2.9.8/docs/000077500000000000000000000000001510172470100142535ustar00rootroot00000000000000keychain-2.9.8/docs/release-steps.md000066400000000000000000000130311510172470100173470ustar00rootroot00000000000000# Keychain Release Steps This document defines the standard release process. Releases use **numeric tags only** (no leading `v`). Example: `2.9.6`. ## 1. When to Bump - Patch (X.Y.Z -> X.Y.Z+1): Documentation, branding, hardening w/o behavior change. - Minor (X.Y -> X.Y+1): User-visible new features, option additions. - Major (X -> X+1): Backward-incompatible changes, removed options. ## 2. Pre-Flight Checklist 1. Working tree clean (`git status`). 2. Update `ChangeLog.md`: add new section at top: `## keychain (

)`. 3. Update `VERSION` file to match new version. 4. Ensure only intentional `funtoo.org` references (historical note in docs only). 5. Decide if any last-minute man page edits are required. ## 3. Build Artifacts Manual build (optional; `make release` now auto-rebuilds prerequisites): ``` make clean && make dist/keychain-$(cat VERSION).tar.gz ``` `make release` or `make release-refresh` will ensure these artifacts exist automatically. Artifacts: - `keychain` (executable wrapper, not committed) - `keychain.1` (man page) - `keychain.spec` - `keychain.txt` - `dist/keychain-.tar.gz` ## 4. Local Sanity Tests ``` ./keychain --version ./keychain --help | head -20 grep -R "github.com/funtoo/keychain" . && echo "(should be zero results)" ``` Check man page header `.TH` line for correct date/version and updated center URL (GitHub canonical). ## 5. Tagging Signed (preferred): ``` git tag -s $(cat VERSION) -m "$(cat VERSION)" ``` Unsigned: ``` git tag $(cat VERSION) ``` Push: ``` git push git push --tags ``` ## 6. Orchestrated Release Path (Preferred) Run: ``` make release # for first publication ``` You will see: 1. Local build presence check (or build via prerequisites). 2. CI artifact fetch (MANDATORY). Failure to retrieve artifacts aborts; you must wait for the workflow to finish. 3. Normalized comparison phase (LOCAL vs CI build): * `keychain` – raw sha256 digest compare. * `keychain.1` – raw hash first; if different, re-compare with the Pod::Man auto-generated first line stripped. A normalized match counts as a match (header differences ignored). * `dist/keychain-.tar.gz` – unpack both tarballs; compare sorted file list and per-file sha256 (man page internally also normalized on first line). Blob-level tar/gzip metadata differences (mtime, uid, compression variance) are ignored if internal contents match. Outcome: - If all artifacts match (raw or normalized) -> Release uses the CI artifact files directly (local artifacts remain untouched for auditing). - If any real content mismatch exists -> Abort. - Override (discouraged) to force publish local artifacts despite mismatch: `KEYCHAIN_FORCE_LOCAL=1 make release` (Use corresponding `... make release-refresh` for refresh mode.) 4. Display of generated release notes (ChangeLog excerpt + provenance table preview). 5. Y/N confirmation prompt. 6. Release creation (or refresh) + asset upload + release notes (re)generation with provenance table via GitHub API. ## 7. Automated Path (Tag-Driven Workflow) Pushing a tag matching `X.Y.Z` triggers `.github/workflows/release.yml` which: - Validates `VERSION` matches tag. - Builds artifacts inside a Debian container. - Extracts ChangeLog section into `.release-notes.md`. - Uploads a private workflow artifact bundle (NOT a published GitHub Release). Publication only occurs when you run `make release` (or refresh) locally; CI never auto-publishes. ## 8. Fast-Fail vs Refresh Targets: - `make release` – Orchestrated create (fails if release exists) with digest validation & confirmation. - `make release-refresh` – Same flow but updates existing release assets AND regenerates release notes (including provenance table). Both require `GITHUB_TOKEN` (repo scope) exported in the environment. ## 9. Refresh Scenario Workflow If you forgot something (docs only, same version): ``` # Edit ChangeLog.md (if you need to adjust text; refresh will regenerate release notes from current ChangeLog plus provenance.) # Rebuild if needed (optional): make dist/keychain-$(cat VERSION).tar.gz make release-refresh ``` You will again get CI fetch attempt, comparisons, preview, and prompt. If functional change needed after publishing: bump version, amend ChangeLog, retag. ## 10. Rollback If a bad tag was pushed: ``` git push origin :refs/tags/ # Optionally delete the GitHub release in the UI. # Fix issues, retag and push again. ``` ## 11. Future Hardening (Planned) - ShellCheck + POSIX lint gating before release. - GPG signing of tarball & man page. - Audit target (`make audit-brand`) to fail on unexpected deprecated domains. - Security hardening sweep (tracked separately). ## 12. Changelog Extraction (Reference) Pseudo-command used by workflow: ``` version=$(cat VERSION) awk -v ver="$version" '/^## keychain 'ver' /{f=1;print;next} /^## keychain /&&f && $0 !~ ver {exit} f' ChangeLog.md ``` ## 13. Verification Matrix | Item | Location | Must Match | |------|----------|------------| | Version tag | git tag | `VERSION` file | | Wrapper script | `keychain` | contains version string | | Man page header | `keychain.1` | version/date/center URL | | Tarball name | `dist/keychain-.tar.gz` | version | ## 14. Minimal Quick Release Recap ``` $EDITOR ChangeLog.md VERSION make clean && make dist/keychain-$(cat VERSION).tar.gz ./keychain --version git tag -s $(cat VERSION) -m "$(cat VERSION)" git push && git push --tags # Create GitHub release, upload assets ``` --- Maintained as of 06 Sep 2025 (CI artifacts canonical: local artifacts are never overwritten; only source path selection differs). keychain-2.9.8/keychain.pod000066400000000000000000000774621510172470100156420ustar00rootroot00000000000000=head1 NAME keychain - Manager for ssh-agent, gpg-agent and private keys. Compatible with POSIX systems. =head1 SYNOPSIS S S<--extended --gpg2 --help --ignore-missing --list --noask --nocolor --nogui> S<--noinherit --nolock --quick --quiet --ssh-allow-forwarded --ssh-allow-gpg> S<--ssh-rm --ssh-spawn-gpg --systemd --version ] [ --ssh-agent-socket I ]> S<[ --dir I ] [ --host I ] [ --lockwait I ]> S<[ --stop I ] [ --timeout I ] [ --wipe I ] [ keys... ]> =head1 INTRODUCTION B helps you to manage SSH and GPG keys in a convenient and secure manner. It acts as a frontend to C and C, but allows you to easily have one long-running C process per system, rather than the norm of one C per login session. This dramatically reduces the number of times you need to enter your passphrase. With C, you only need to enter a passphrase once every time your local machine is rebooted. Keychain also makes it easy for remote cron jobs to securely "hook in" to a long running C process, allowing your scripts to take advantage of key-based logins. Keychain also supports GnuPG 2.1 and later, and will automatically start gpg-agent if any GPG keys are referenced on the command-line, and will ensure these credentials are cached in memory and available for use. Official project home: L. =head1 COMPATIBILITY Keychain supports most UNIX-like operating systems and intentionally limits its scope to modern, widely deployed implementations of OpenSSH and GnuPG. =over 4 =item * Minimum OpenSSH version: 7.3 OpenSSH 7.3 (released 2016-08-01) introduced the C<-G> (config dump) option used by Keychain to expand C extended keys and the C<--confallhosts> feature. Because this option is central to modern key discovery, Keychain officially targets OpenSSH 7.3 or newer. Earlier versions (>=6.5) may appear to work for basic usage (loading explicitly named keys) but are not part of the supported test matrix. Key types supported under the 7.3+ baseline include RSA, ECDSA and Ed25519 (Ed25519 first appeared in OpenSSH 6.5). DSA keys have been disabled by default upstream since OpenSSH 7.0; Keychain will still list or load them if they are explicitly enabled and present, but they are considered legacy. Features leveraged from OpenSSH: ssh -V (implementation detection) ssh -nG host (configuration expansion for host: extended keys) ssh-agent -s (Bourne-compatible environment output; always forced) ssh-agent -t (default key lifetime when spawning) ssh-agent -a path (explicit socket path when requested) ssh-add -l/-L (list fingerprints / public keys) ssh-add -d/-D (remove one / remove all keys) ssh-add -c (confirmation mode) ssh-add -t (per-key lifetime override) ssh-keygen -l -f (fingerprint extraction, handles SHA256 & legacy MD5) Forwarded agent sockets (optionally adopted with --ssh-allow-forwarded) =item * GnuPG / gpg-agent GnuPG 2.1 or later is required for gpg-agent integration. Keychain uses C C and C to detect, adopt or (optionally) spawn gpg-agent when acting as an SSH agent via C<--ssh-allow-gpg> or C<--ssh-spawn-gpg>. Earlier GnuPG versions (2.0.x) lacked reliable SSH key storage support and are not supported. =item * Shells The Keychain script itself requires a POSIX / Bourne-compatible shell. It generates pidfiles for Bourne (C<...-sh>), csh/tcsh (C<...-csh>) and fish (C<...-fish>) shells. Internally Keychain now always consumes the canonical Bourne output of ssh-agent (forced with C<-s>) and derives the other shell formats. Bash, ksh and zsh consume the Bourne pidfiles. Keychain is officially supported on Git Bash (MSYS2) for Windows, including proper handling of Windows-style paths and usernames containing spaces. The csh/tcsh and fish pidfiles remain supported for backward compatibility. They are candidates for deprecation: a warning about planned removal will be announced at least two minor releases before any change. =item * Spaces in HOME and paths Keychain 2.9.7+ properly handles home directories and private key paths containing spaces. All path variables in pidfiles (SSH_AUTH_SOCK) are quoted to ensure correct parsing by shells. The implementation uses a robust approach that evaluates ssh-agent output in a subshell to extract environment variables directly, rather than fragile string parsing. This ensures compatibility even if ssh-agent output format changes. File ownership detection uses POSIX-defined ls output format with logic to correctly parse usernames containing spaces. =item * Legacy / Other SSH implementations SunSSH (found on older Oracle Solaris releases) may still function; code paths for SunSSH detection are retained but are not actively tested. They are scheduled for review and possible removal after three future minor releases unless community feedback requests retention. The historical commercial ssh.com implementation and its agent are no longer targets. If detected, behavior may fall back to simpler key loading; advanced features (confirmation, modern fingerprint parsing, host expansion) are not guaranteed. Reporting such usage via GitHub Issues will help shape any future support decisions. =item * Systemd user environment On systems with systemd --user, Keychain can inject C (and optionally C) via C<--systemd> for wider session availability. =back Summary: A supported environment is OpenSSH >= 7.3 plus (optionally) GnuPG >= 2.1, running under any modern UNIX-like OS with a POSIX shell. Earlier versions or unlisted SSH implementations may work for basic scenarios but are outside official scope. =head1 LIFECYCLE Typically, you configure keychain to run when you first log in to a system. If you are using Bourne shell or bash, you will create a F<~/.profile> or F<~/.bash_profile> file and include the following line in it: eval "$(keychain --eval id_rsa)" Keychain will start ssh-agent if one isn't already running. Keychain then checks to make sure your private keys (in this example, "id_rsa") are loaded into the agent. If they are not, you are prompted for any passphrase necessary to decrypt them, so that they are cached in memory and available for use. In addition to printing some user-friendly output to your terminal, keychain will also output important ssh-agent environment variables, which the S<"$( )"> (you can also use S<"` `">) captures, and the "eval" evaluates, setting these variables in your current shell. These ssh-agent environment variables are also written to F<~/.keychain/${HOSTNAME}-sh>, so that subsequent logins and non-interactive shells such as cron jobs can source the file to access the running ssh-agent and make passwordless ssh connections using the cached private keys -- even when you are logged out. These files are collectively called B. The key files specified on the command-line will be searched for in the F<~/.ssh/> directory, and keychain will expect to find the private key file with the same name, as well as a C<.pub> public key. Keychain will also see if any GPG keys are specified, and if so, prompt for any passphrases to cache these keys into C. Typically, private SSH key files are specified by filename only, without path, although it is possible to specify an absolute or relative path to the private key file as well. Private key files can be symlinks to the actual key as long as your system has the C command available. More advanced features are available for specifying keys as well -- see the B<--extended> and B<--confallhosts> options for more information. In addition, for GPG keys specified, similar steps will be taken to ensure that gpg-agent has the GPG key cached in memory and ready for use. =head1 STREAMLINING AND SIMPLIFICATION Keychain 2.9.0 has been streamlined, and with this maintenance several command-line options have been retired as they are not completely necessary. This simplifies the use of the tool by making it more intuitive to use. The files created in F<~/.keychain> have also been cleaned up. This section details all the important changes. =head2 PIDFILE CHANGES "Pidfile" is the nickname for files created in F<~/.keychain> which can then be sourced by your scripts to access a running agent. When using gpg-agent for GPG keys, keychain will no longer create a F<~/.keychain/${HOSTNAME}-sh-gpg> pidfile. This file is no longer needed as the canonical GPG socket inside F<~/.gnupg/> will be used to detect the running gpg-agent, which is the modern convention. GnuPG 2.1 and later have stopped using environment variables to find the agent, so we follow this upstream change. =head2 IMPROVED DEBUGGING A new B<--debug> option is now available which will print additional information related to keychain's decisions regarding why and how an agent was found -- or not. =head2 NEW (AND DEPRECATED) OPTIONS This section provides an overview of new and deprecated command-line options. For full details on each option, see the respective option definition under L. =head3 Keychain 2.9.2 The C<--confhost> option has been deprecated. Instead of C<--confhost hostname>, use C<--extended host:hostname>. This extended format allows multiple keys of multiple types (SSH, GPG and SSH-from-hostname) to be specified on the command-line together. This is also fully compatible with C<--confallhosts>, and keychain now de-duplicates the list of keys to be loaded. =head3 Keychain 2.9.1 The short-lived C<--ssh-wipe> and C<--gpg-wipe> options that appeared only in version 2.9.0 were replaced with C<--wipe I> to work similarly to the C<--stop> option, and allows you to specify "ssh", "gpg" or "all". =head3 Keychain 2.9.0 The C<--agents> option is now deprecated. Keychain will always ensure an ssh-agent or equivalent is running, or if C<--ssh-spawn-gpg> is used, potentially a gpg-agent for the purpose of storing SSH keys if no running ssh-agent is available. To simply use a gpg-agent if one is already running, falling back to launching ssh-agent if no agent is available, use the C<--ssh-allow-gpg> option. Specifying a GPG key on the command-line will instruct keychain to enable gpg-agent functionality automatically. This eliminates an option that you need to specify (C<--agents ssh,gpg>) when invoking keychain, and for many will avoid spawning a GPG agent you may not be using. Specifying the C<--agents> option will now display a warning that it's deprecated, but keychain will not abort. The C<--inherit> option, which took one of four arguments, has been deprecated. Keychain's default behavior remains that of preferring to use an ssh-agent or equivalent referenced by its pidfile, falling back to finding an ssh-agent in its environment. By default, keychain will not use a gpg-agent socket for SSH keys unless at least C<--ssh-allow-gpg> is specified. Similarly, the use of any forwarded SSH agent connection is disabled by default and can be enabled via C<--ssh-allow-forwarded>. Again, see the full documentation for each option in the L section. You can still influence keychain's behavior via the still-present C<--noinherit> option which will prevent all detection of existing SSH agents via the environment. The C<--clear> option is still available, but isn't intended to be a "standalone" option, meaning that it is used to perform an initial clearing of cached keys before loading any specified keys when Keychain is run. To perform the sole action of wiping all cached keys, use the C<--wipe> action. To remove an individual cached SSH private key or keys, use the C<--ssh-rm> I option. The C<--stop> option will now only stop any running ssh-agent processes, and still supports three possible options: "mine", "others" and "all". It no longer stops gpg-agent processes, which tend to get auto-respawned by GPG tools, so killing gpg-agent typically doesn't make a lot of sense. =head2 BETTER GNUPG INTEGRATION Keychain can now use an existing gpg-agent that has been started in your environment to store ssh keys, rather than spawning its own ssh-agent, by using the C<--ssh-allow-gpg> option. If you would like keychain to spawn gpg-agent instead of ssh-agent, and use it to store SSH keys, specify the "--ssh-spawn-gpg" option. Without either option, keychain will not use an SSH_AUTH_SOCK that is provided by gpg-agent, and will spawn an official ssh-agent process. In addition, behind the scenes, keychain now uses the gpg-connect-agent executable to restart the agent, get official PID and socket information, etc. Please note that while gpg-agent provides full compatibility with ssh-agent, its password prompt is handled by pinentry and its store may encrypt your in-memory keys. For this reason, consider this new feature experimental, and use GitHub issues to report back any anomalies or suggested improvements for gpg-agent integration. =head2 DISPLAY CHANGES When keychain uses gpg-agent for either GnuPG or SSH support, then keychain will display the GnuPG socket file in its output, rather than the PID. Since the socket file has the F<~/.gnupg> path in it, this communicates to you that gpg-agent, not ssh-agent, is active. If you see an integer PID, this means that ssh-agent is being used. =head2 STREAMLINED STARTUP By default, keychain will always ensure that an ssh-agent should be started. It will only start a gpg-agent if a GPG key is referenced on the command-line. Modern versions of gpg-agent also support the caching of SSH keys, allowing it to be a drop-in replacement for ssh-agent. With keychain 2.9.0, a new "--ssh-spawn-gpg" option has been added, which when specified will give keychain permission to spawn a gpg-agent in place of ssh-agent. =head2 CODE OPTIMIZATION With keychain 2.9.0, there has been significant code cleanup, reducing the size of the script from 1500 lines to about 1100 lines. In addition, the script is now fully compliant with L, which will be hugely helpful to ensure continued POSIX shell compatibility moving forward. =head1 AGENT DETECTION AND STARTUP ALGORITHM This section documents the official algorithm used for detecting and if necessary starting ssh-agent, to facilitate understanding as well as developer maintenance of the codebase. =head2 DEFINITIONS There are several important definition related to the algorithm: =over =item 1. The B, "ssh-agent", which is a long-running daemon. This can also in some cases be "gpg-agent", depending on command-line options. =item 2. The B, which is the agent or forwarded agent that was not started by keychain, but is detected in the environment or by other means. =item 3. The B, which is an B whose information has been persisted by being written to the B (see below). =item 4. The B C, which points to the socket file used to communicate with ssh-agent, and optionally C, which indicates its process ID if running locally (although gpg-agent does not define C, even if running locally.) =item 5. The file F<~/.keychain/${HOSTNAME}-sh> and related files, which are collectively referred to as "B". Pidfiles are used to persistently store C and C environment variables for use by other scripts, as well as by keychain itself, formatted so that they can be "sourced" by shells of various types. =item 6. Relevant B affecting behavior, which include B<--noinherit>, B<--ssh-allow-gpg>, B<--ssh-spawn-gpg>, and B<--ssh-allow-forwarded>. =back =head2 ALGORITHM OVERVIEW When the keychain script is run, it will first attempt to find a running ssh-agent. =over =item Phase 1: pidfile: To do this, it will first look for an existing B. If one exists, it will be inspected and used to find a running agent, in alignment with specified B. If this process is successful, a "keychain-spawned" agent is found and this process is complete. Otherwise, we continue to the next step. =item Phase 2: environment: If keychain's B did not yield a running agent, keychain looks at B defined in the current environment. This step will be skipped if the B<--noinherit> option is specified. If an agent is found that is in alignment with specified B, it is considered "B" by keychain, and the process is complete. Otherwise, we continue to the next step. =item Phase 3: spawn agent: In the absence of finding a keychain-spawned or existing agent that can be adopted, keychain will spawn a new ssh-agent, or a new gpg-agent if B<--ssh-spawn-gpg> is specified and gpg-agent is available. =item Final Phase: update pidfile: In addition, the Bs will be updated to reflect the keychain-spawned or B<"inherited"> agent. An B<"inherited"> agent, once written to the B, is now considered to be B<"adopted">. =item Pidfile update exception: If the B<--ssh-allow-forwarded> option was specified, and a forwarded SSH socket was found -- which is identified as a valid SSH socket defined in a C variable, which has no associated or valid C also defined in the environment and is also determined to not be the socket of any running gpg-agent -- then this agent will simply be used, B in any B. This is because this SSH-supplied socket will disappear when the underlying SSH connection terminates, and thus it cannot be relied on to be available persistently. =back =head2 THE QUICK SHORT-CIRCUIT When the B<--quick> option is specified, a special algorithm will run prior to the main agent-detection algorithm listed above. A pidfile, if it exists, will be evaluated as per Phase 1 of the main algorithm. If a valid running agent is found, it will be queried for valid keys. If at least one valid key is loaded into the agent, the quick start is considered successful, and keychain will skip the regular agent startup algorithm, and will use this found agent. =head2 SUMMARY AND RATIONALE The keychain ssh-agent detection and startup algorithm is somewhat sophisticated for a reason. There is an intention behind its behavior. The algorithm has been specifically designed to prefer an agent spawned by keychain, or previously adopted, if that agent is currently available. This is by design, because other system software could spawn ssh-agent and/or gpg-agent processes, and we want keychain to not coerced into using these new agents which may suddenly appear in the environment unexpectedly when new desktop sessions start and in other circumstances. If keychain is too "suggestible", it will lose track of the agent which currently holds valid keys, which can result in unnecessary prompting for passphrases, and general confusion. =head1 OPTIONS =over =item B<--absolute> This option can be used with the B<--dir> option, if you would like to specify a non-default directory to store pidfiles (defaults to F<~/.keychain>). When this option is used, the script does not automatically append F to the path, allowing you to use any arbitrary directory name for the storing of pidfiles. Please note that Keychain 2.9.3 adds some extra security checks related to directory and file permissions -- you must have exclusive ownership of any directory that keychain uses to store pidfiles, or keychain will abort. =item B<--clear> When specified, this option adds an initial step prior to adding any keys to the agents of wiping all existing cached keys/passphrases. This is intended to be used alongside keychain --eval to ensure that only the specified keys are loaded, and that keychain should assume that you are an intruder until proven otherwise and force all interactive logins to specify valid passphrases. This option increases security and still allows your cron jobs to use your ssh keys when you're logged out. =item B<--confallhosts> In addition to any keys specified on the command-line, this option will tell keychain to scour F<~/.ssh/config> for all private keys referenced in all C lines, and load all keys for all hosts. =item B<--confirm> Keys are subject to interactive confirmation by the SSH_ASKPASS program before being used for authentication. See the -c option for ssh-add(1). =item B<--debug> Keychain 2.9.0 introduces the B<--debug> option, which will output additional information related to how Keychain makes its agent-selection process. Specifically, it will output when an B is rejected because it is being supplied by gpg-agent -- and this is not allowed due to no B<--ssh-allow-gpg> option, or when it is rejected because it appears to be from a forwarded SSH connection, and B<--ssh-allow-forwarded> was not supplied. =item B<--dir> I This option allows you to use another directory besides F<$HOME/.keychain> for the storing of pidfiles. Please note that Keychain 2.9.3 adds some extra security checks related to directory and file permissions -- you must have exclusive ownership of any directory that keychain uses to store pidfiles, or the script will abort. Also see the B<--absolute> option. =item B<--env> I After parsing options, keychain will load additional environment settings from "filename". By default, if "--env" is not given, then keychain will attempt to load from F<~/.keychain/[hostname]-env> or alternatively F<~/.keychain/env>. The purpose of this file is to override settings such as PATH, in case ssh is stored in a non-standard place. =item B<--eval> Keychain will print lines to be evaluated in the shell on stdout. It respects the SHELL environment variable to determine if Bourne shell or C shell output is expected. =item B<--extended> This enables extended command-line key processing with more features, and is a replacement for the old C<--confhost> option. When specified, each key specified on the command-line must have a prefix to explicitly categorize it. SSH keys must have a prefix of "sshk:" immediately followed by the path or key name (the part after the "sshk:" is processed just like a SSH key is without the C<--extended> option). GPG keys must be in the format "gpgk:" immediately followed by the 8 or 16-character fingerprint. If "host:" is specified, then Keychain will extract the SSH configuration for the specified hostname, grab all identityfile options (private keys) specified, and these keys will be included in the set of keys to be loaded by keychain. This allows multiple keys of multiple types, including SSH-keys-by-host, to be specified together, which wasn't possible with C<--confhost>. =item B<--gpg2> This option changes the default gpg calls to use gpg2 instead to support distributions such as Ubuntu which has both gpg and gpg2 =item B<-h --help> Show help that looks remarkably like this man-page. As of 2.6.10, help is sent to stdout so it can be easily piped to a pager. =item B<--host> I Set alternate hostname for creation of pidfiles =item B<--ignore-missing> Don't warn if some keys on the command-line can't be found. This is useful for situations where you have a shared .bash_profile, but your keys might not be available on every machine where keychain is run. =item B<-l --list> List signatures of all active SSH keys, and exit, similar to "ssh-add -l". =item B<-L --list-fp> List fingerprints of all active SSH keys, and exit, similar to "ssh-add -L". =item B<--lockwait> I How long to wait for the lock to become available. Defaults to 5 seconds. Specify a value of zero or more. If the lock cannot be acquired within the specified number of seconds, then this keychain process will forcefully acquire the lock. =item B<--noask> This option tells keychain do everything it normally does (ensure ssh-agent is running, set up the F<~/.keychain/[hostname]-{c}sh> files) except that it will not prompt you to add any of the keys you specified if they haven't yet been added to ssh-agent. =item B<--nocolor> Disable color highlighting for non ANSI-compatible terms. =item B<--nogui> Don't honor SSH_ASKPASS, if it is set. This will cause ssh-add to prompt on the terminal instead of using a graphical program. =item B<--noinherit> Don't inherit any agent processes, overriding the default behavior of inheriting all non-forwarded ssh-agent and any existing gpg-agent processes. Also see L. =item B<--nolock> Don't attempt to use a lockfile while manipulating files, pids and keys. =item B<--query> Keychain will print lines in KEY=value format representing the values which are set by the agents. =item B<-Q --quick> If an ssh-agent process is running then use it. Don't verify the list of keys, other than making sure it's non-empty. This option avoids locking when possible so that multiple terminals can be opened simultaneously without waiting on each other. See the L section for more information regarding how this fits into the overall startup algorithm. =item B<-q --quiet> Only print messages in case of warning, error or required interactivity. As of version 2.6.10, this also suppresses "Identities added" messages for ssh-agent. =item B<-k --stop> I Kill currently running ssh-agent processes and exit. Note that previous versions of keychain (2.8.5 and earlier) allowed killing of gpg-agent as well. This functionality was removed as ssh-agent and gpg-agent have a bit different design philosophies and you almost always only have at most one gpg-agent running at a time. Use "killall gpg-agent" if you really want to kill gpg-agent. However, since this option also removes pidfiles, it will remove any gpg-agent processes adopted by keychain that were being used to store ssh keys. The following values are valid for "which" which controls which ssh-agents to target: =over 9 =item all Kill all ssh-agent processes and quit keychain immediately. Prior to keychain-2.5.0, this was the behavior of the bare "--stop" option. =item others Kill agent processes other than the ones keychain is providing. Prior to keychain-2.5.0, keychain would do this automatically. The new behavior requires that you specify it explicitly if you want it. =item mine Kill keychain's agent processes, leaving other agents alone. =back =item B<--ssh-agent-socket> I Use this option to specify the path to the socket file that you would like ssh-agent to create and use as its official socket. By default, ssh-agent will create its own socket file, typically in /tmp. =item B<--ssh-allow-forwarded> By default, keychain will not use a forwarded ssh-agent connection, which is a ssh-agent socket created by SSH that has no associated local process. To permit keychain to use a forwarded ssh-agent connection, specify this option. If a SSH-forwarded socket is used, it will not be persisted in the pidfiles, as it is not likely to be available outside of the currently-active SSH session. =item B<--ssh-allow-gpg> Would you like to have keychain use an already-running gpg-agent to store your SSH keys, rather than spawning a new ssh-agent? This option does just that. When this option is specified, keychain will accept an SSH_AUTH_SOCK environment variable in its environment, even if it was created by gpg-agent. Modern versions of gpg-agent are also able to store SSH keys. By default, keychain has a special check to avoid using a gpg-agent that has set the SSH_AUTH_SOCK environment variable, and will instead spawn its own ssh-agent. With this option enabled, this restriction is turned off. Please note that this option does not actually instruct keychain to I a gpg-agent for storing SSH keys if no agent is available -- if you want that, see the B<--ssh-spawn-gpg> option, below. ALSO NOTE: When a gpg-agent is adopted for ssh-agent duties in this way, the F<~/.keychain/${HOSTNAME}-sh> pidfile will be updated to reference the gpg-agent socket, so it will be seamlessly used by future cron jobs needing an ssh-agent, as well as by future invocations of keychain, as long as the B<--ssh-allow-gpg> or B<--ssh-spawn-gpg> (which implies B<--ssh-allow-gpg>) are specified. =item B<--ssh-spawn-gpg> This is the option to use if you're really on-board with using gpg-agent as a replacement for ssh-agent. Not only will keychain use a running gpg-agent if found as per the B<--ssh-allow-gpg>, but if it needs to spawn a new ssh-agent, it will go ahead and spawn a gpg-agent in its place, and use it instead. Also see notes for the B<--ssh-allow-gpg> option, as this option also implies B<--ssh-allow-gpg>. =item B<--ssh-rm -r> I Only perform the single action of removing the specified cached keys from the running ssh-agent, and then exit. =item B<--systemd> Inject environment variables into the systemd --user session. =item B<--timeout> I Allows a timeout to be set for identities added to ssh-agent. When this option is used with a keychain invocation that starts ssh-agent itself, then keychain uses the appropriate ssh-agent option to set the default timeout for ssh-agent. The --timeout option also gets passed to ssh-add invocations, so any keys added to a running ssh-agent will be individually configured to have the timeout specified, overriding any ssh-agent default. Most users can simply use the timeout setting they desire and get the result they want -- with all identities having the specified timeout, whether added by keychain or not. More advanced users can use one invocation of keychain to set the default timeout, and optionally set different timeouts for keys added by using a subsequent invocation of keychain. =item B<-V --version> Show version information. =item B<--wipe> I Only perform the single action of wiping all agent's cached keys. Specify 'ssh', 'gpg' or 'all' for SSH keys, GPG keys and all agents respectively. Also see the C<--ssh-rm> action and the C<--clear> option. =back =head1 EXAMPLES This snippet should work in most shells to load two ssh keys and one gpg key: eval `keychain --eval id_rsa id_dsa 0123ABCD` For the fish shell, use the following format: if status --is-interactive keychain --eval --quiet -Q id_rsa | source end If you have trouble with that in csh: setenv SHELL /bin/csh eval `keychain --eval id_rsa id_dsa 0123ABCD` This is equivalent for Bourne shells (including bash and zsh) but doesn't use keychain's --eval feature: keychain id_rsa id_dsa 0123ABCD [ -z "$HOSTNAME" ] && HOSTNAME=`uname -n` [ -f $HOME/.keychain/$HOSTNAME-sh ] && \ . $HOME/.keychain/$HOSTNAME-sh This is equivalent for C shell (including tcsh): keychain id_rsa id_dsa 0123ABCD host=`uname -n` if (-f $HOME/.keychain/$host-csh) then source $HOME/.keychain/$host-csh endif Likewise, the following commands can be used in fish: keychain id_rsa id_dsa 0123ABCD test -z "$hostname"; and set hostname (uname -n) if test -f "$HOME/.keychain/$hostname-fish" source $HOME/.keychain/$hostname-fish end To load keychain variables from a script (for example from cron) and abort unless id_dsa is available: # Load keychain variables and check for id_dsa [ -z "$HOSTNAME" ] && HOSTNAME=`uname -n` . $HOME/.keychain/$HOSTNAME-sh 2>/dev/null ssh-add -l 2>/dev/null | grep -q id_dsa || exit 1 =head1 SEE ALSO L, L, L, L =head1 NOTES Keychain was created and is currently maintained by Daniel Robbins. To report a bug or request an enhancement, use the issue tracker at L. Keychain includes bash completion support for command-line options, SSH keys, GPG keys, and extended mode prefixes (C, C, C). The completion script is included in the source tarball at C. For installation instructions, see the README file or run C. The former Funtoo Linux wiki page is preserved only as an historical reference: L. keychain-2.9.8/keychain.sh000077500000000000000000001040701510172470100154570ustar00rootroot00000000000000#!/bin/sh versinfo() { qprint qprint " Copyright ${CYANN}2002-##CUR_YEAR##${OFF} Daniel Robbins, BreezyOps" qprint " lockfile() Copyright ${CYANN}2009${OFF} Parallels, Inc." qprint " Copyright ${CYANN}2007${OFF} Aron Griffis" qprint " Copyright ${CYANN}2002-2006${OFF} Gentoo Foundation" qprint qprint " Keychain is free software: you can redistribute it and/or modify" qprint " it under the terms of the ${CYANN}GNU General Public License version 2${OFF} as" qprint " published by the Free Software Foundation." qprint } umask 0077 NEWLINE=" " version=##VERSION## PATH="${PATH}${PATH:+:}/usr/bin:/bin:/sbin:/usr/sbin:/usr/ucb" unset pidfile_out unset myaction havelock=false unset hostopt extended=false confallhosts=false ignoreopt=false noaskopt=false noguiopt=false nolockopt=false lockwait=5 openssh=unknown sunssh=unknown quickopt=false quietopt=false clearopt=false allow_inherited=true color=true unset stopwhich unset timeout unset ssh_agent_socket unset ssh_timeout unset sshavail unset sshkeys unset gpgkeys unset cmdline_keys keydir="${HOME}/.keychain" unset envf evalopt=false confirmopt=false absoluteopt=false systemdopt=false unset ssh_confirm unset GREP_OPTIONS gpg_prog_name="gpg" gpg_started=false ssh_allow_forwarded=false ssh_allow_gpg=false ssh_spawn_gpg=false debugopt=false CYAN="" CYANN="" GREEN="" RED="" PURP="" YEL="" OFF="" # GNU awk and sed have regex issues in a multibyte environment. If any locale # variables are set, then override by setting LC_ALL unset pinentry_locale if [ -n "$LANG$LC_ALL" ] || locale 2>/dev/null | grep -E -qv '="?(|POSIX|C)"?$' 2>/dev/null; then # save LC_ALL so that pinentry-curses works right. This has always worked # correctly for me but peper and kloeri had problems with it. pinentry_lc_all="$LC_ALL" LC_ALL=C export LC_ALL fi qprint() { # shellcheck disable=SC2048,SC2086 $quietopt || echo "$@" >&2; return 0 } mesg() { # general information; suppressed with --quiet qprint " ${GREEN}*${OFF} $*" } warn() { # important warning; not suppressed with --quiet # shellcheck disable=SC2048,SC2086 echo " ${RED}* Warning${OFF}: "$* >&2 } note() { # important notice; suppressed with --quiet # shellcheck disable=SC2048,SC2086 qprint " ${YEL}* Note${OFF}: "$* >&2 } debug() { # shellcheck disable=SC2048,SC2086 $debugopt && echo " ${CYAN}debug>" $*"${OFF}" >&2; return 0 } error() { # shellcheck disable=SC2048,SC2086 echo " ${RED}* Error${OFF}:" $* >&2 } die() { [ -n "$1" ] && error "$*" qprint $evalopt && { echo; echo "false;"; } exit 1 } helpinfo() { cat >&1 < owner="drobbins", group="drobbins", size=4096 # - "Mathew Binkley 197609 4096" -> owner="Mathew Binkley", group="197609", size=4096 # # We distinguish by checking if the field after potential owner+space is numeric: # If field 5 is NOT numeric, then field 4 is part of the owner name (space in username). # If field 5 IS numeric, then field 4 is the group name (no space in username). get_owner() { go_path="$1" # shellcheck disable=SC2012 # Using ls -ld for POSIX-defined formatted output; not parsing ls in a loop ls -ld "$go_path" 2>/dev/null | awk '{ result = $3 if (NF >= 5 && $5 !~ /^[0-9]+$/) { result = result " " $4 } print result }' } # synopsis: testssh # Figure out which ssh is in use, set the global boolean $openssh and $sunssh testssh() { # Query local host for SSH application, presently supporting OpenSSH and Sun SSH: openssh=false sunssh=false case "$(ssh -V 2>&1)" in *OpenSSH*) openssh=true ;; *Sun?SSH*) sunssh=true ;; esac # See if gpg-agent is available and provides ssh-agent functionality: if $ssh_spawn_gpg; then if ! out="$(gpg-agent --help | grep enable-ssh-support)" || [ -z "$out" ]; then warn "gpg-agent ssh functionality not available; not using..." ssh_spawn_gpg=false fi fi } # synopsis: verifykeydir # Make sure the key dir is set up correctly. Exits on error. verifykeydir() { # Create keydir if it doesn't exist already if [ -f "${keydir}" ]; then die "${keydir} is a file (it should be a directory)" # Solaris 9 doesn't have -e; using -d.... elif [ ! -d "${keydir}" ]; then mkdir "${keydir}" || die "can't create ${keydir}" fi dir_owner="$(get_owner "${keydir}")" [ "$dir_owner" != "$me" ] && warn "${keydir} is owned by ${dir_owner}, not ${me}. Please fix." # shellcheck disable=SC2012 # POSIX defines the first 9 chars of ls -l: [ "$(ls -ld "${keydir}" | cut -c5-10)" != "------" ] && warn "Keychain dir has lax permissions. Use ${CYAN}chmod -R go-rwx '${keydir}'${OFF} to fix." if ! :> "$pidf.foo"; then die "can't write inside $pidf" else rm -f "$pidf.foo" fi } lockfile() { # This function originates from Parallels Inc.'s OpenVZ vpsreboot script. # Description: This function attempts to acquire the lock. If it succeeds, # it returns 0. If it fails, it returns 1. This function retuns immediately # and only tries to acquire the lock once. tmpfile="$lockf.$$" echo $$ >"$tmpfile" 2>/dev/null || exit if ln "$tmpfile" "$lockf" 2>/dev/null; then rm -f "$tmpfile" havelock=true && return 0 fi if kill -0 "$(cat "$lockf" 2>/dev/null)" 2>/dev/null; then rm -f "$tmpfile" return 1 fi if ln "$tmpfile" "$lockf" 2>/dev/null; then rm -f "$tmpfile" havelock=true && return 0 fi rm -f "$tmpfile" "$lockf" && return 1 } takelock() { # Description: This function calls lockfile() multiple times if necessary # to try to acquire the lock. It returns 0 on success and 1 on failure. # Change in behavior: if timeout expires, we will forcefully acquire lock. [ "$havelock" = "true" ] && return 0 [ "$nolockopt" = "true" ] && return 0 # First attempt: lockfile && return 0 counter=0 mesg "Waiting $lockwait seconds for lock..." while [ "$counter" -lt "$(( lockwait * 10 ))" ] do lockfile && return 0 sleep 0.1; counter=$(( counter + 1 )) done rm -f "$lockf" && lockfile && return 0 return 1 } # synopsis: droplock # Drops the lock if we're holding it. droplock() { $havelock && [ -n "$lockf" ] && rm -f "$lockf" } # synopsis: findpids [prog] # Returns a space-separated list of agent pids. # prog can be ssh or gpg, defaults to ssh. Note that if another prog is ever # added, need to pay attention to the length for Solaris compatibility. findpids() { fp_prog=${1-ssh} unset fp_psout # Different systems require different invocations of ps. Try to generalize # the best we can. The only requirement is that the agent command name # appears in the line, and the PID is the first item on the line. if [ -z "$OSTYPE" ]; then OSTYPE=$(uname) || die 'uname failed' fi # Try systems where we know what to do first case "$OSTYPE" in AIX|*bsd*|*BSD*|CYGWIN|darwin*|Linux|linux-gnu|OSF1) fp_psout=$(ps x 2>/dev/null) ;; # BSD syntax HP-UX) fp_psout=$(ps -u "$me" 2>/dev/null) ;; # SysV syntax SunOS) case $(uname -r) in [56]*) fp_psout=$(ps -u "$me" 2>/dev/null) ;; # SysV syntax *) fp_psout=$(ps x 2>/dev/null) ;; # BSD syntax esac ;; GNU|gnu) fp_psout=$(ps -g 2>/dev/null) ;; # GNU Hurd syntax esac # If we didn't get a match above, try a list of possibilities... # The first one will probably fail on systems supporting only BSD syntax. if [ -z "$fp_psout" ]; then # shellcheck disable=SC2009 fp_psout=$(UNIX95=1 ps -u "$me" -o pid,comm 2>/dev/null | grep '^ *[0-9]+') [ -z "$fp_psout" ] && fp_psout=$(ps x 2>/dev/null) [ -z "$fp_psout" ] && fp_psout=$(ps w 2>/dev/null) # Busybox syntax fi # Return the list of pids; ignore case for Cygwin. # Check only 8 characters since Solaris truncates at that length. # Ignore defunct ssh-agents (bug 28599) if [ -n "$fp_psout" ]; then echo "$fp_psout" | \ awk "BEGIN{IGNORECASE=1} /defunct/{next} /$fp_prog-[a]gen/{print \$1}" | xargs return 0 fi # If none worked, we're stuck error "Unable to use \"ps\" to scan for $fp_prog-agent processes" error "Please report to https://github.com/danielrobbins/keychain/issues." return 1 } stop_ssh_agents() { mesg "Stopping ssh-agent(s)..." takelock || die [ "$stopwhich" != all ] && eval "$(catpidf_shell sh)" # get SSH_AGENT_PID if defined ssh_pids=$(findpids ssh) || die if [ -z "$ssh_pids" ]; then mesg "No ssh-agent(s) found running" elif [ "$stopwhich" = all ]; then # shellcheck disable=SC2086 kill $ssh_pids >/dev/null 2>&1 mesg "All ${CYANN}$me${OFF}'s ssh-agents stopped: ${CYANN}$ssh_pids${OFF}" elif [ -n "$SSH_AGENT_PID" ]; then if [ "$stopwhich" = mine ]; then kill "$SSH_AGENT_PID" >/dev/null 2>&1 mesg "Keychain ssh-agents stopped: ${CYANN}$SSH_AGENT_PID${OFF}" else # others for ssh_pid in $ssh_pids; do [ "$ssh_pid" = "$SSH_AGENT_PID" ] && continue kill "$ssh_pid" >/dev/null 2>&1 killed_pids="$killed_pids $ssh_pid" done mesg "Other ${CYANN}$me${OFF}'s ssh-agents stopped:${CYANN}$killed_pids${OFF}" fi else mesg "No keychain ssh-agent found running" fi # remove pid files if keychain-controlled if [ "$stopwhich" != others ]; then rm -f "${pidf}" "${cshpidf}" "${fishpidf}" 2>/dev/null fi qprint && exit 0 } # synopsis: catpidf_shell shell # cat the pid file for the specified shell. catpidf_shell() { case "$1" in */fish|fish) cp_pidf="$fishpidf" ;; *csh) cp_pidf="$cshpidf" ;; *) cp_pidf="$pidf" ;; esac if [ ! -f "$cp_pidf" ]; then debug "pidfile doesn't exist"; return 1 else cat "${cp_pidf}"; echo; return 0 fi } startagent_gpg() { if $gpg_started; then return 0 else gpg_started=true fi if gpg_agent_sock="$( echo "GETINFO socket_name" | gpg-connect-agent --no-autostart | head -n1 | sed -n 's/^D //;1p' )" && [ -S "$gpg_agent_sock" ]; then mesg "Using existing gpg-agent: ${CYANN}$gpg_agent_sock${OFF}" pidfile_out="SSH_AUTH_SOCK=$gpg_agent_sock; export SSH_AUTH_SOCK" # make sure we adopt it else gpg_opts="--daemon" [ -n "${timeout}" ] && gpg_opts="$gpg_opts --default-cache-ttl $(( timeout * 60 )) --max-cache-ttl $(( timeout * 60 ))" $ssh_spawn_gpg && gpg_opts="$gpg_opts --enable-ssh-support" mesg "Starting gpg-agent..." # shellcheck disable=SC2086 # this is intentional pidfile_out="$(gpg-agent --sh $gpg_opts)" return $? fi } ssh_envcheck() { # Initial short-circuits for known abort cases: [ -z "$SSH_AUTH_SOCK" ] && return 1 if [ ! -S "$SSH_AUTH_SOCK" ]; then debug "SSH_AUTH_SOCK in $1 is invalid; ignoring it" unset SSH_AUTH_SOCK && return 1 fi # Throw away the PID with a devug warning if it's invalid: if [ -n "$SSH_AGENT_PID" ] && ! kill -0 "$SSH_AGENT_PID" >/dev/null 2>&1; then unset SSH_AGENT_PID && debug "SSH_AGENT_PID in $1 is invalid; ignoring it" fi # Now, find potential agents: if [ -z "$SSH_AGENT_PID" ]; then # There are some cases where we can accept a socket without an associated SSH_AGENT_PID: if gpg_socket="$(echo "GETINFO ssh_socket_name" | gpg-connect-agent --no-autostart 2>/dev/null | head -n1 | sed -n 's/^D //;1p' )"; then if [ "$gpg_socket" = "$SSH_AUTH_SOCK" ]; then if $ssh_allow_gpg; then $quickopt || mesg "Using ssh-agent ($1): ${CYANN}$gpg_socket${OFF} (GnuPG)" return 0 else unset SSH_AUTH_SOCK && debug "Ignoring SSH_AUTH_SOCK -- this is the GnuPG-supplied socket" && return 1 fi fi fi if $ssh_allow_forwarded; then SSH_AGENT_PID="forwarded" $quickopt || mesg "Using ${GREEN}forwarded${OFF} ssh-agent: ${GREEN}$SSH_AUTH_SOCK${OFF}" return 0 else unset SSH_AUTH_SOCK && debug "Ignoring SSH_AUTH_SOCK -- this is a forwarded socket" && return 1 fi else # We have valid SSH_AGENT_PID, so we accept the socket too: $quickopt || mesg "Existing ssh-agent ($1): ${CYANN}$SSH_AGENT_PID${OFF}" return 0 fi } # synopsis: startagent_ssh # This function specifically handles (potential) starting of ssh-agent. Unlike the # classic startagent function, it does not handle writing out contents of pidfiles, # which will be done in a combined way after startagent_gpg() is called as well. startagent_ssh() { if $quickopt; then if ( unset SSH_AGENT_PID SSH_AUTH_SOCK && eval "$(catpidf_shell sh)" && ssh_envcheck quick && ssh_l > /dev/null ); then mesg "Found existing populated ssh-agent (quick)" return 0 else if ( eval "$(catpidf_shell sh)" && ssh_envcheck quick ); then note "Quick start unsuccessful -- no keys loaded..." else note "Quick start unsuccessful -- no agent found..." fi quickopt=false fi fi takelock || die # See if our pidfile is valid without wiping env: if ( unset SSH_AGENT_PID SSH_AUTH_SOCK && eval "$(catpidf_shell sh)" && ssh_envcheck pidfile ); then # Our pidfile is valid! :) We can simply use it: debug "pidfile is valid" && unset SSH_AGENT_PID SSH_AUTH_SOCK && eval "$(catpidf_shell sh)" elif $allow_inherited && ssh_envcheck env; then # If our env is OK, then let's grab it for our pidfile, as long as we don't have a forwarded ssh connection: if [ "$SSH_AGENT_PID" != forwarded ]; then pidfile_out="SSH_AUTH_SOCK=$SSH_AUTH_SOCK; export SSH_AUTH_SOCK" if [ -n "$SSH_AGENT_PID" ]; then pidfile_out="$pidfile_out SSH_AGENT_PID=$SSH_AGENT_PID; export SSH_AGENT_PID;" fi fi else # spawn, we must... rm -f "${pidf}" "${cshpidf}" "${fishpidf}" 2>/dev/null # pidfile is either non-existant or invalid if $ssh_spawn_gpg; then startagent_gpg ssh # this function will set pidfile_out itself return $? else mesg "Starting ssh-agent..." # shellcheck disable=SC2086 # We purposely don't want to double-quote the args to ssh-agent so they disappear if not used: pidfile_out="$(ssh-agent -s ${ssh_timeout} ${ssh_agent_socket})" return $? fi fi } write_pidfile() { if [ -n "$pidfile_out" ]; then pidfile_out=$(echo "$pidfile_out" | grep -v 'Agent pid') case $pidfile_out in setenv\ *) error "unexpected csh-style ssh-agent output (expected -s)"; exit 1;; esac rm -f "$pidf" "$cshpidf" "$fishpidf" # Remove first, so we can recreate with our umask # Robust approach: eval the output in a subshell to extract actual variable values # This avoids fragile string parsing and handles any ssh-agent output format changes wp_auth_sock=$(eval "$pidfile_out" >/dev/null 2>&1; echo "$SSH_AUTH_SOCK") wp_agent_pid=$(eval "$pidfile_out" >/dev/null 2>&1; echo "$SSH_AGENT_PID") # Write sh format - quote SSH_AUTH_SOCK to handle spaces, SSH_AGENT_PID is numeric { [ -n "$wp_auth_sock" ] && echo "SSH_AUTH_SOCK=\"${wp_auth_sock}\"; export SSH_AUTH_SOCK" [ -n "$wp_agent_pid" ] && echo "SSH_AGENT_PID=${wp_agent_pid}; export SSH_AGENT_PID;" } >"$pidf" # Write csh format { [ -n "$wp_auth_sock" ] && echo "setenv SSH_AUTH_SOCK \"${wp_auth_sock}\";" [ -n "$wp_agent_pid" ] && echo "setenv SSH_AGENT_PID ${wp_agent_pid};" } >"$cshpidf" # Write fish format { [ -n "$wp_auth_sock" ] && echo "set -e SSH_AUTH_SOCK; set -x -U SSH_AUTH_SOCK \"${wp_auth_sock}\";" [ -n "$wp_agent_pid" ] && echo "set -e SSH_AGENT_PID; set -x -U SSH_AGENT_PID ${wp_agent_pid};" } >"$fishpidf" else debug skipping creation of pidfiles! fi } # synopsis: extract_fingerprints # Extract the fingerprints from standard input, returns space-separated list. # Utility routine for ssh_l and ssh_f extract_fingerprints() { while read -r ef_line; do case "$ef_line" in *\ *\ [0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]:*) # Sun SSH spits out different things depending on the type of # key. For example: # md5 1024 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 /home/barney/.ssh/id_dsa(DSA) # 2048 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 /home/barney/.ssh/id_rsa.pub echo "$ef_line" | cut -f3 -d' ' ;; *\ [0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]:*) # The more consistent OpenSSH format, we hope # 1024 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 /home/barney/.ssh/id_dsa (DSA) echo "$ef_line" | cut -f2 -d' ' ;; *\ [A-Z0-9][A-Z0-9]*:[A-Za-z0-9+/][A-Za-z0-9+/]*) # The new OpenSSH 6.8+ format, # 1024 SHA256:mVPwvezndPv/ARoIadVY98vAC0g+P/5633yTC4d/wXE /home/barney/.ssh/id_dsa (DSA) echo "$ef_line" | cut -f2 -d' ' ;; *) # Fall back to filename. Note that commercial ssh is handled # explicitly in ssh_l and ssh_f, so hopefully this rule will # never fire. warn "Can't determine fingerprint from the following line, falling back to filename" mesg "$ef_line" basename "$ef_line" | sed 's/[ (].*//' ;; esac done | xargs } # synopsis: ssh_l # Return space-separated list of known fingerprints ssh_l() { sl_mylist=$(ssh-add -l 2>/dev/null) sl_retval=$? if $openssh; then # Error codes: # 0 success # 1 OpenSSH_3.8.1p1 on Linux: no identities (not an error) # OpenSSH_3.0.2p1 on HP-UX: can't connect to auth agent # 2 can't connect to auth agent case $sl_retval in 0) echo "$sl_mylist" | extract_fingerprints ;; 1) case "$sl_mylist" in *"open a connection"*) sl_retval=2 ;; esac ;; esac return $sl_retval elif $sunssh; then # Error codes (from http://docs.sun.com/db/doc/817-3936/6mjgdbvio?a=view) # 0 success (even when there are no keys) # 1 error case $sl_retval in 0) echo "$sl_mylist" | extract_fingerprints ;; 1) case "$sl_mylist" in *"open a connection"*) sl_retval=2 ;; esac ;; esac return $sl_retval else # Error codes: # 0 success - however might say "The authorization agent has no keys." # 1 can't connect to auth agent # 2 bad passphrase # 3 bad identity file # 4 the agent does not have the requested identity # 5 unspecified error if [ $sl_retval = 0 ]; then # Output of ssh-add -l: # The authorization agent has one key: # id_dsa_2048_a: 2048-bit dsa, agriffis@alpha.zk3.dec.com, Fri Jul 25 2003 10:53:49 -0400 # Since we don't have a fingerprint, just get the filenames *shrug* echo "$sl_mylist" | sed '2,$s/:.*//' | xargs fi return $sl_retval fi } # synopsis: ssh_f filename # Return fingerprint for a keyfile # Requires $openssh or $sunssh ssh_f() { sf_filename="$1" if $openssh || $sunssh; then realpath_bin="$(command -v realpath)" # if private key is symlink and symlink to *.pub is missing: if [ -L "$sf_filename" ] && [ -n "$realpath_bin" ]; then sf_filename="$($realpath_bin "$sf_filename")" fi lsf_filename="$sf_filename.pub" if [ ! -f "$lsf_filename" ]; then # try to remove extension from private key, *then* add .pub, and see if we now find it: if [ -L "$sf_filename" ] && [ -n "$realpath_bin" ]; then sf_filename="$($realpath_bin "$sf_filename")" fi lsf_filename=$(echo "$sf_filename" | sed 's/\.[^\.]*$//').pub if [ ! -f "$lsf_filename" ]; then note "Cannot find separate public key for $1." lsf_filename="$sf_filename" fi fi sf_fing=$(ssh-keygen -l -f "$lsf_filename") || return 1 echo "$sf_fing" | extract_fingerprints else # can't get fingerprint for ssh2 so use filename *shrug* basename "$sf_filename" fi return 0 } # synopsis: gpg_listmissing # Accepts piped input from stdin. Returns a newline-separated list of keys found to be missing. gpg_listmissing() { unset glm_missing GPG_TTY=$(tty) while IFS= read -r glm_k; do [ -z "$glm_k" ] && continue # Check if this key is known to the agent. Don't know another way... if env -i GPG_TTY="$GPG_TTY" PATH="$PATH" GPG_AGENT_INFO="$GPG_AGENT_INFO" "${gpg_prog_name}" --no-autostart --no-options --use-agent --no-tty --sign --local-user "$glm_k" -o- >/dev/null 2>&1 " if they exist or "miss:" otherwise. all_host_identities() { if [ ! -e ~/.ssh/config ]; then warn "No ~/.ssh/config -- can't extract host identities" && return fi while IFS= read -r line; do case $line in *[Ii][Dd][Ee][Nn][Tt][Ii][Tt][Yy][Ff][Ii][Ll][Ee]*) keyf="$(echo "$line" | awk '{print $2}')" if [ -f "$keyf" ]; then echo "sshk:${keyf}" else echo "miss:${keyf}" fi esac done < ~/.ssh/config } # Synopsis: this is the default logic for categorizing command-line keys. If a file is # specified and is found in ~/.ssh, or just exists, it's a SSH key. If gpg recognizes it, # then it's a GPG key. Otherwise, it's a missing key. cmdline_keys_to_extkey() { while read -r pm_k; do [ -z "$pm_k" ] && continue if [ -f "$pm_k" ]; then echo "sshk:$pm_k" elif [ -f "$HOME/.ssh/$pm_k" ]; then echo "sshk:$HOME/.ssh/$pm_k" elif "${gpg_prog_name}" --list-secret-keys "$pm_k" >/dev/null 2>&1; then echo "gpgk:$pm_k" else echo "miss:$pm_k" fi done } # Synopsis: sees if specified stdin $keyf exists; converts to "sshk:" or "miss:" lines keyf_expand() { while read -r keyf; do if [ -f "$keyf" ]; then echo "sshk:$keyf" else echo "miss:$keyf" fi done } # Synopsis: We allow sshk:id_rsa from the command-line, with no path, but this needs # to be expanded to the actual filename internally -- or "miss:". Logic is a bit different # so we can't use cmdline_keys_to_extkey() code. sshk_fixup() { while read -r extkey; do key_pref="$(echo "$extkey" | cut -b1-5)" if [ "$key_pref" != "sshk:" ]; then echo "$extkey" else pm_k="$(echo "$extkey" | cut -b6-)" if [ -f "$pm_k" ]; then echo "sshk:$pm_k" elif [ -f "$HOME/.ssh/$pm_k" ]; then echo "sshk:$HOME/.ssh/$pm_k" else echo "miss:$pm_k" fi fi done } # Synopsis: performs final processing on extended keys. Currently converts each "host:" # extkeys to (possibly many) "sshk:" or "miss:" lines. Also validates all keys for basic # syntax. extkey_expand() { while read -r extkey; do [ -z "$extkey" ] && continue key_pref="$(echo "$extkey" | cut -b1-5)" if [ "$key_pref" = "host:" ]; then ssh -nG "$(echo "$extkey" | cut -b6-)" 2>/dev/null | grep -e ^identityfile | awk '{print $2}' | keyf_expand elif [ "$key_pref" = "sshk:" ] || [ "$key_pref" = "gpgk:" ] || [ "$key_pref" = "miss:" ]; then echo "$extkey" else warn "Unrecognized extended key \"$extkey\". Should have a sshk:, gpgk: or host: prefix." fi done } # Synopsis: gets all extended keys. SSH keys are in "sshk:" format. GPG fingerprints # are in "gpgk:" format. Any SSH keys that cannot be found are expanded to "miss:, # which is used for warnings later. If --extended is specified, we expect "sshk:foo" format on # the command-line. Otherwise, we use cmdline_keys_to_extkey() to convert the standard command- # line arguments into a format that keychain internals expect. get_all_extkeys() { if $confallhosts; then all_host_identities fi if ! $extended; then echo "$cmdline_keys" | cmdline_keys_to_extkey | extkey_expand else echo "$cmdline_keys" | sshk_fixup | extkey_expand fi } setaction() { if [ -n "$myaction" ]; then die "you can't specify --$myaction and $1 at the same time" else myaction="$1" fi } wantagent() { [ "$1" = "gpg" ] && [ -n "$gpgkeys" ] && return 0 return 1 } gpg_wipe() { out="$( echo RELOADAGENT | gpg-connect-agent --no-autostart 2>/dev/null )" if [ "$out" = "OK" ]; then mesg "gpg-agent: All identities removed." else mesg "gpg-agent: Could not remove identities; possibly not running. (output: $out)" fi } ssh_wipe() { if sshout=$(ssh-add -D 2>&1); then mesg "ssh-agent: $sshout" else warn "ssh-agent: $sshout" fi } while [ -n "$1" ]; do case "$1" in --absolute) absoluteopt=true ;; --agents) shift; warn "--agents is deprecated, ignoring." ;; --confhost) die "--confhost is deprecated; use \"${CYANN}--extended host:${OFF}\" instead." ;; --confallhosts) confallhosts=true ;; --confirm) confirmopt=true ;; --debug|-D) debugopt=true ;; --eval) evalopt=true ;; --extended|--ext|-e) extended=true ;; --gpg2) gpg_prog_name="gpg2" ;; --help|-h) setaction help ;; --host) shift; hostopt="$1" ;; --ignore-missing) ignoreopt=true ;; --inherit) shift; warn "--inherit is deprecated, ignoring. Use --ssh-allow-forwarded, --noinherit as needed instead.";; --list|-l) setaction list ;; --list-fp|-L) setaction list-fp ;; --noask) noaskopt=true ;; --nocolor) color=false ;; --nogui) noguiopt=true ;; --noinherit) allow_inherited=false ;; --nolock) nolockopt=true ;; --query) setaction query; quietopt=true ;; --quiet|-q) quietopt=true ;; --ssh-allow-gpg) ssh_allow_gpg=true ;; --ssh-spawn-gpg) ssh_spawn_gpg=true; ssh_allow_gpg=true ;; --ssh-agent-socket) shift; ssh_agent_socket="-a $1" ;; --ssh-allow-forwarded) ssh_allow_forwarded=true ;; --ssh-rm|-r) setaction ssh_rm ;; --systemd) systemdopt=true ;; --version|-V) setaction version ;; --attempts) warn "--attempts is now deprecated." ;; --clear) clearopt=true $quickopt && die "--quick and --clear are not compatible" ;; --dir) shift case "$1" in */.*) keydir="$1" ;; '') die "--dir requires an argument" ;; *) if $absoluteopt; then keydir="$1" else keydir="$1/.keychain" # be backward-compatible fi ;; esac ;; --env) shift if [ -z "$1" ]; then die "--env requires an argument" else envf="$1" fi ;; --lockwait) shift if [ "$1" -ge 0 ] 2>/dev/null; then lockwait="$1" else die "--lockwait requires an argument zero or greater." fi ;; --quick|-Q) quickopt=true $clearopt && die "--quick and --clear are not compatible" ;; --stop|-k) setaction stop case $2 in all|mine|others) stopwhich="$2" ;; *) die "Please specify 'all', 'mine' or 'others' for --stop" ;; esac ;; --timeout) shift if [ "$1" -gt 0 ] 2>/dev/null; then timeout=$1 else die "--timeout requires a numeric argument greater than zero" fi ;; --wipe) shift case $1 in gpg) setaction gpg_wipe ;; ssh) setaction ssh_wipe ;; all) setaction all_wipe ;; *) die "Please specify ssh, gpg or all for --wipe action" esac ;; -*) zero=$(basename "$0") echo "$zero: unknown option $1" >&2 $evalopt && { echo; echo "false;"; } exit 1 ;; *) cmdline_keys="$1${NEWLINE}${cmdline_keys}" ;; esac shift done if [ -z "$hostopt" ]; then if [ -z "$HOSTNAME" ]; then hostopt=$(uname -n 2>/dev/null || echo unknown) else hostopt="$HOSTNAME" fi fi pidf="${keydir}/${hostopt}-sh" cshpidf="${keydir}/${hostopt}-csh" fishpidf="${keydir}/${hostopt}-fish" lockf="${keydir}/${hostopt}-lockf" for keyf in "$pidf" "$cshpidf" "$fishpidf"; do if [ -f "$keyf" ]; then # shellcheck disable=SC2012 # POSIX defines the first 9 chars of ls -l: go_modes="$(ls -ld "${keyf}" | cut -c5-10 )" [ "$go_modes" != "------" ] && warn "Some pidfiles have lax permissions. Use ${CYAN}chmod -R go-rwx '${keydir}'${OFF} to fix." keyf_owner="$(get_owner "${keyf}")" [ -n "$keyf_owner" ] && [ "$keyf_owner" != "$me" ] && warn "${keyf} is owned by ${keyf_owner}, not ${me}. Please fix." fi done # Read the env snippet (especially for things like PATH, but could modify basically anything) if [ -z "$envf" ]; then envf="${keydir}/${hostopt}-env" [ -f "$envf" ] || envf="${keydir}/env" [ -f "$envf" ] || unset envf fi if [ -n "$envf" ]; then # shellcheck disable=SC1090 . "$envf" fi # Don't use color if there's no terminal on stderr if [ -n "$OFF" ]; then tty <&2 >/dev/null 2>&1 || color=false fi $color || unset BLUE CYAN CYANN GREEN PURP OFF RED # TODO: we can't assume pidfile has been created yet? Or not a big deal? [ "$myaction" = list ] && eval "$(catpidf_shell sh)" && exec ssh-add -l [ "$myaction" = list-fp ] && eval "$(catpidf_shell sh)" && exec ssh-add -L qprint #initial newline mesg "${PURP}keychain ${OFF}${CYANN}${version}${OFF} ~ ${GREEN}https://github.com/danielrobbins/keychain${OFF}" [ "$myaction" = version ] && { versinfo; exit 0; } [ "$myaction" = help ] && { versinfo; helpinfo; exit 0; } # Don't use signal names because they don't work on Cygwin. if $clearopt; then trap '' 2 # disallow ^C until we've had a chance to --clear trap 'droplock; exit 1' 1 15 # drop the lock on signal trap 'droplock;' 0 # drop the lock on exit else # Don't use signal names because they don't work on Cygwin. trap 'droplock; exit 1' 1 2 15 # drop the lock on signal trap 'droplock;' 0 # drop the lock on exit fi testssh # sets $openssh, $sunssh and tweaks $ssh_spawn_gpg verifykeydir # sets up $keydir # --stop: kill the existing ssh-agent(s) (not gpg-agent) and quit [ "$myaction" = stop ] && stop_ssh_agents # --timeout translates almost directly to ssh-add/ssh-agent -t, but ssh.com uses # minutes and OpenSSH uses seconds if [ -n "$timeout" ]; then ssh_timeout=$timeout if $openssh || $sunssh; then ssh_timeout=$(( ssh_timeout * 60 )) fi ssh_timeout="-t $ssh_timeout" fi all_keys="$(get_all_extkeys | sort -u)" if ! $ignoreopt; then for key in $(echo "$all_keys" | grep ^miss:); do warn "Can't find key \"${GREEN}$( echo "$key" | cut -c6- )${OFF}\"" done fi sshkeys="$(echo "$all_keys" | sed -n '/^sshk:/s/sshk://p')" gpgkeys="$(echo "$all_keys" | sed -n '/^gpgk:/s/gpgk://p')" if [ "$myaction" = gpg_wipe ]; then gpg_wipe; qprint; exit 0 elif [ "$myaction" = ssh_wipe ]; then ssh_wipe; qprint; exit 0 elif [ "$myaction" = all_wipe ]; then ssh_wipe; gpg_wipe; qprint; exit 0 elif [ "$myaction" = query ]; then # --query displays current settings, but does not start an agent: if catpidf_shell sh > /dev/null; then catpidf_shell sh | cut -d\; -f1 && exit 0 else die "Can't query. Does pidfile exist?" fi elif [ "$myaction" = ssh_rm ]; then if [ -n "$sshkeys" ]; then die "No ssh keys specified to remove." fi for key in $sshkeys; do if sshout=$(ssh-add -d "$key" 2>&1); then mesg "ssh-agent key $key removed." else die "keychain was unable to remove ssh-agent key $key. output: $sshout" fi done qprint; exit 0 else # This will start gpg-agent as an ssh-agent if such functionality is enabled (default) startagent_ssh || warn "Unable to start an ssh-agent (error code: $?)" [ -n "$pidfile_out" ] && write_pidfile && eval "$pidfile_out" > /dev/null if ! $gpg_started && wantagent gpg; then # If we also want gpg, and it hasn't been started yet, start it also. We don't need to # look for pidfile output, as this would have been output from the startagent_ssh->startagent_gpg # call above, and gpg doesn't use pidfiles for gpg stuff anymore. startagent_gpg || warn "Unable to start gpg-agent (error code: $?)" fi if $clearopt; then ssh_wipe if wantagent gpg; then gpg_wipe fi trap 'droplock' 2 # done clearing, safe to ctrl-c fi fi if $evalopt; then catpidf_shell "$SHELL" fi $systemdopt && systemctl --user set-environment "SSH_AUTH_SOCK=$SSH_AUTH_SOCK" $systemdopt && [ -n "$SSH_AGENT_PID" ] && systemctl --user set-environment "SSH_AGENT_PID=$SSH_AGENT_PID" # These options don't need to load keys, so terminate early: $noaskopt && { qprint; exit 0; } $quickopt && { qprint; exit 0; } load_ssh_keys() { missing="$(echo "${sshkeys}" | ssh_listmissing)" savedisplay="$DISPLAY" if $confirmopt; then if $openssh || $sunssh; then ssh_confirm=-c else warn "--confirm only works with OpenSSH" fi fi # Put $missing into args to access $# and other goodies. Since $missing is a line-delimited # list of files with (potentially) spaces, we must do an IFS hack to get each file in # $1, $2, $3, etc. For Bourne-shell compatibility, we don't have another good option: IFS_BAK="$IFS"; IFS="$NEWLINE" # shellcheck disable=SC2086 set -- $missing IFS="$IFS_BAK" [ $# -eq 0 ] && return mesg "Adding ${CYANN}$#${OFF} ssh key(s): ${CYANN}$*${OFF}" if $noguiopt || [ -z "$SSH_ASKPASS" ] || [ -z "$DISPLAY" ]; then unset DISPLAY # DISPLAY="" can cause problems unset SSH_ASKPASS # make sure ssh-add doesn't try SSH_ASKPASS fi # shellcheck disable=SC2086 sshout=$(ssh-add ${ssh_timeout} ${ssh_confirm} "$@" 2>&1) ret=$? if [ $ret = 0 ]; then blurb="" [ -n "$timeout" ] && blurb="life=${timeout}m" [ -n "$timeout" ] && $confirmopt && blurb="${blurb}," $confirmopt && blurb="${blurb}confirm" [ -n "$blurb" ] && blurb=" (${blurb})" mesg "ssh-add: Identities added: $sshkeys${blurb}" else warn "ssh-add failed: (return code: $ret; output: $sshout)" fi [ -n "$savedisplay" ] && DISPLAY="$savedisplay" return $ret } load_gpg_keys() { $noguiopt && unset DISPLAY [ -n "$DISPLAY" ] || unset DISPLAY # DISPLAY="" can cause problems GPG_TTY=$(tty) ; export GPG_TTY # fall back to ncurses pinentry for key in "$@"; do [ -z "$key" ] && continue mesg "Adding gpg key: $key" # the 3>&1, etc. is a temp fd to allow us to capture stderr, while throwing away stdout which is encrypted data, and avoid a "null byte on input" bash warning: gpgout="$(env LC_ALL="$pinentry_lc_all" "${gpg_prog_name}" --no-autostart --no-options --use-agent --sign --local-user "$key" -o- 3>&1 1>/dev/null 2>&3 URL: https://github.com/danielrobbins/keychain Source0: %{name}-%{version}.tar.bz2 License: GPL v2 Group: Applications/Internet BuildArch: noarch Requires: /bin/sh coreutils Recommends: bash-completion Prefix: /usr/bin BuildRoot: %{_tmppath}/%{name}-root %description Keychain is a manager for OpenSSH and GnuPG agents. It acts as a front-end to ssh-agent and gpg-agent, allowing you to easily have one long-running agent process per system, rather than per login session. This dramatically reduces the number of times you need to enter your passphrase from once per new login session to once every time your local machine is rebooted. Keychain also makes it easy for remote cron jobs to securely hook into a long-running ssh-agent process, and integrates with gpg-agent for unified key management. %prep %setup -q %build %install [ $RPM_BUILD_ROOT != / ] && rm -rf $RPM_BUILD_ROOT mkdir -p $RPM_BUILD_ROOT/%{_bindir} $RPM_BUILD_ROOT/%{_mandir}/man1 mkdir -p $RPM_BUILD_ROOT/%{_datadir}/bash-completion/completions install -m0755 keychain $RPM_BUILD_ROOT/%{_bindir}/keychain install -m0644 keychain.1 $RPM_BUILD_ROOT/%{_mandir}/man1 install -m0644 completions/keychain.bash $RPM_BUILD_ROOT/%{_datadir}/bash-completion/completions/keychain %clean rm -rf $RPM_BUILD_ROOT %files %defattr(-,root,root) %{_bindir}/* %doc %{_mandir}/*/* %doc ChangeLog COPYING.txt keychain.pod README.md %{_datadir}/bash-completion/completions/keychain keychain-2.9.8/scripts/000077500000000000000000000000001510172470100150125ustar00rootroot00000000000000keychain-2.9.8/scripts/fetch-ci-artifacts.sh000077500000000000000000000031261510172470100210130ustar00rootroot00000000000000#!/bin/sh # Fetch latest workflow artifacts for the given version tag using the GitHub API. # Usage: GITHUB_TOKEN=... GITHUB_REPOSITORY=owner/repo ./scripts/fetch-ci-artifacts.sh set -eu VER=${1:?usage: fetch-ci-artifacts.sh } DEST=${2:?usage: fetch-ci-artifacts.sh } REPO=${GITHUB_REPOSITORY:?GITHUB_REPOSITORY not set} [ -n "${GITHUB_TOKEN:-}" ] || { echo "GITHUB_TOKEN not set" >&2; exit 1; } # Find workflow run for this tag (latest by created_at) # We assume workflow file name 'release.yml'. RUNS_JSON=$(curl -fsSL -H "Authorization: Bearer $GITHUB_TOKEN" "https://api.github.com/repos/$REPO/actions/runs?per_page=50") RUN_ID=$(printf '%s' "$RUNS_JSON" | jq -r --arg ver "$VER" '.workflow_runs | map(select(.head_branch == $ver or .display_title == $ver or .head_sha != null)) | map(select(.name=="release")) | map(select(.head_branch==$ver)) | sort_by(.created_at) | last.id') [ "$RUN_ID" != "null" ] || { echo "No workflow run found for tag $VER" >&2; exit 1; } ARTIFACTS=$(curl -fsSL -H "Authorization: Bearer $GITHUB_TOKEN" "https://api.github.com/repos/$REPO/actions/runs/$RUN_ID/artifacts") ART_ID=$(printf '%s' "$ARTIFACTS" | jq -r '.artifacts[] | select(.name | test("keychain-")) | .id' | tail -1) [ -n "$ART_ID" ] || { echo "No artifacts found for run $RUN_ID" >&2; exit 1; } TMPZIP=$(mktemp) curl -fsSL -H "Authorization: Bearer $GITHUB_TOKEN" -L "https://api.github.com/repos/$REPO/actions/artifacts/$ART_ID/zip" -o "$TMPZIP" mkdir -p "$DEST" unzip -qo "$TMPZIP" -d "$DEST" rm -f "$TMPZIP" echo "Fetched CI artifacts for $VER into $DEST" >&2 keychain-2.9.8/scripts/release-common.sh000077500000000000000000000023261510172470100202620ustar00rootroot00000000000000#!/bin/sh # Common helper functions for release automation. # Requires: curl, jq (jq optional for nicer parsing; if absent we try raw parsing.) set -eu api() { # api [datafile] method=$1; shift path=$1; shift url="https://api.github.com/repos/${GITHUB_REPOSITORY}${path}" if [ $# -gt 0 ]; then datafile=$1; shift curl -fsSL -X "$method" \ -H "Authorization: Bearer ${GITHUB_TOKEN}" \ -H "Accept: application/vnd.github+json" \ --data "@${datafile}" \ "$url" else curl -fsSL -X "$method" \ -H "Authorization: Bearer ${GITHUB_TOKEN}" \ -H "Accept: application/vnd.github+json" \ "$url" fi } redact() { sed -E 's/[A-Za-z0-9_]{20,}/[REDACTED]/g'; } need() { command -v "$1" >/dev/null 2>&1 || { echo "Missing required tool: $1" >&2; exit 1; }; } extract_notes() { ver=$1 awk -v ver="$ver" 'BEGIN{printed=0} /^## keychain "ver" /{printed=1;print;next} /^## keychain / && printed {exit} printed {print}' ChangeLog.md } fail() { echo "Error: $*" >&2; exit 1; } # Validate environment [ -n "${GITHUB_TOKEN:-}" ] || fail "GITHUB_TOKEN not set" [ -n "${GITHUB_REPOSITORY:-}" ] || fail "GITHUB_REPOSITORY not set (e.g. danielrobbins/keychain)" need curl keychain-2.9.8/scripts/release-create.sh000077500000000000000000000043011510172470100202300ustar00rootroot00000000000000#!/bin/sh # Create a new GitHub release (fails if it exists) and upload assets. set -eu VER=${1:?usage: release-create.sh } GITHUB_REPOSITORY=${GITHUB_REPOSITORY:-danielrobbins/keychain} . "$(dirname "$0")/release-common.sh" [ "$(cat VERSION)" = "$VER" ] || fail "VERSION file mismatch ($(cat VERSION) != $VER)" notes_file=$(mktemp) ./scripts/release-notes.sh "$VER" "$notes_file" # Artifact path vars (provided by orchestrator if using CI artifacts) ASSET_KEYCHAIN=${KEYCHAIN_ASSET_KEYCHAIN:-keychain} ASSET_MAN=${KEYCHAIN_ASSET_MAN:-keychain.1} ASSET_TARBALL=${KEYCHAIN_ASSET_TARBALL:-dist/keychain-$VER.tar.gz} echo "Creating release $VER" json=$(mktemp) cat >"$json" </dev/null || fail "Failed to create release (maybe it already exists?)" echo "Uploading assets..." for f in "$ASSET_TARBALL" "$ASSET_KEYCHAIN" "$ASSET_MAN"; do [ -f "$f" ] || fail "Missing asset file $f" # Determine publish name (basename should remain canonical filenames) case $(basename "$f") in "keychain-$VER.tar.gz") pname="keychain-$VER.tar.gz";; keychain) pname="keychain";; keychain.1) pname="keychain.1";; *) # If path is different (e.g., CI dir), map by type heuristics if echo "$f" | grep -q "keychain-$VER.tar.gz"; then pname="keychain-$VER.tar.gz"; fi if echo "$f" | grep -q "/keychain$"; then pname="keychain"; fi if echo "$f" | grep -q "/keychain.1$"; then pname="keychain.1"; fi [ -n "${pname:-}" ] || fail "Could not determine asset publish name for $f"; ;; esac curl -sS -X POST \ -H "Authorization: Bearer $GITHUB_TOKEN" \ -H "Content-Type: application/octet-stream" \ --data-binary @"$f" \ "https://uploads.github.com/repos/${GITHUB_REPOSITORY}/releases/$(curl -fsSL -H "Authorization: Bearer $GITHUB_TOKEN" https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/tags/$VER | jq '.id')/assets?name=$pname" >/dev/null echo " uploaded $pname (from $f)" done echo "Release $VER created successfully." keychain-2.9.8/scripts/release-notes.sh000077500000000000000000000024741510172470100201260ustar00rootroot00000000000000#!/bin/sh # Generate release notes body (ChangeLog excerpt + provenance table). # Usage: release-notes.sh # Respects KEYCHAIN_ASSET_* path variables if set (for CI artifact selection). set -eu VER=${1:?usage: release-notes.sh } OUT=${2:?usage: release-notes.sh } [ -f ChangeLog.md ] || { echo "ChangeLog.md not found" >&2; exit 1; } [ "$(cat VERSION)" = "$VER" ] || { echo "VERSION mismatch ($(cat VERSION) != $VER)" >&2; exit 1; } awk -v ver="$VER" '/^## keychain '"$VER"' /{f=1;print;next} /^## keychain / && f && $0 !~ ver {exit} f' ChangeLog.md > "$OUT" [ -s "$OUT" ] || { echo "Failed to extract section for $VER" >&2; exit 1; } ASSET_KEYCHAIN=${KEYCHAIN_ASSET_KEYCHAIN:-keychain} ASSET_MAN=${KEYCHAIN_ASSET_MAN:-keychain.1} if [ -f "$ASSET_KEYCHAIN" ] && [ -f "$ASSET_MAN" ]; then k_sha256=$(sha256sum "$ASSET_KEYCHAIN" | awk '{print $1}') man_sha256=$(sha256sum "$ASSET_MAN" | awk '{print $1}') commit_sha1=$(git rev-list -n1 "$VER" 2>/dev/null || true) { echo echo '---' echo echo '### Build Provenance' echo echo '| Artifact | SHA256 |' echo '|----------|--------|' echo "| keychain | $k_sha256 |" echo "| keychain.1 | $man_sha256 |" echo echo "Tag commit SHA1: \`$commit_sha1\`" } >> "$OUT" fi exit 0 keychain-2.9.8/scripts/release-orchestrate.sh000077500000000000000000000217771510172470100213300ustar00rootroot00000000000000#!/bin/bash # Orchestrated release creation/refresh with: # 1. Local build presence check (already performed via Makefile prereqs) # 2. CI artifact fetch (mandatory) # 3. Digest comparison (local vs CI artifacts) with normalization rules # 4. Selection of artifact SOURCE PATHS (never mutating local originals): # * If all artifacts match (allowing normalized equality) AND no override -> USE CI PATHS # * If KEYCHAIN_FORCE_LOCAL=1 -> USE LOCAL PATHS (even if mismatches) # * Otherwise any real mismatch aborts # 5. Display extracted release notes for confirmation # 6. Create or refresh release using chosen artifact paths # Usage: release-orchestrate.sh create|refresh set -eu MODE=${1:?usage: release-orchestrate.sh create|refresh } VER=${2:?usage: release-orchestrate.sh create|refresh } REPO=${GITHUB_REPOSITORY:-danielrobbins/keychain} [ "$(cat VERSION)" = "$VER" ] || { echo "VERSION file mismatch ($(cat VERSION)) != $VER" >&2; exit 1; } [ -n "${GITHUB_TOKEN:-}" ] || { echo "GITHUB_TOKEN not set" >&2; exit 1; } # Soft fail early if attempting to create a release that already exists. if [ "$MODE" = create ]; then existing_json=$(curl -fsS -H "Authorization: Bearer ${GITHUB_TOKEN}" -H "Accept: application/vnd.github+json" \ "https://api.github.com/repos/${REPO}/releases/tags/$VER" || true) if printf '%s' "$existing_json" | grep -q '"id"'; then echo "Release $VER already exists on GitHub." >&2 echo >&2 echo "Next steps:" >&2 echo " - To update its assets and regenerate notes: make release-refresh" >&2 echo " - To publish a new release: increment VERSION, update ChangeLog.md, retag, then run make release" >&2 echo >&2 exit 1 fi fi # 1. Ensure local assets exist for f in dist/keychain-$VER.tar.gz keychain keychain.1; do [ -f "$f" ] || { echo "Missing local asset: $f" >&2; exit 1; } done # 2. Fetch CI artifacts (MANDATORY) CI_DIR=".ci-artifacts-$VER" rm -rf "$CI_DIR" echo "Fetching CI artifacts for $VER (mandatory step)..." >&2 if ! ./scripts/fetch-ci-artifacts.sh "$VER" "$CI_DIR"; then echo "ERROR: Unable to retrieve CI artifacts for $VER. Release aborted." >&2 echo "Hint: Ensure the GitHub Actions 'release' workflow for tag $VER has completed successfully." >&2 echo " Re-run 'make release' once artifacts are available." >&2 exit 1 fi echo "CI artifacts retrieved." >&2 calc_sha256() { sha256sum "$1" | awk '{print $1}'; } diff_flag=0 echo "Digest comparison (normalized where applicable):" compare_tar_content() { local local_tar=$1 ci_tar=$2 local tmp_local tmp_ci tmp_local=$(mktemp -d) tmp_ci=$(mktemp -d) # Extract quietly tar xzf "$local_tar" -C "$tmp_local" 2>/dev/null || return 2 tar xzf "$ci_tar" -C "$tmp_ci" 2>/dev/null || return 2 # Determine root (expect exactly one directory named keychain-$VER) local root="keychain-$VER" if [ ! -d "$tmp_local/$root" ] || [ ! -d "$tmp_ci/$root" ]; then echo " keychain-$VER.tar.gz: unexpected directory layout inside tar" >&2 return 3 fi # List files (regular only) relative to root local lf cf lf=$( (cd "$tmp_local/$root" && find . -type f -print | LC_ALL=C sort) ) cf=$( (cd "$tmp_ci/$root" && find . -type f -print | LC_ALL=C sort) ) if [ "$lf" != "$cf" ]; then echo " keychain-$VER.tar.gz: file list differs" >&2 return 4 fi # Hash each file local mismatch=0 while IFS= read -r rel; do [ -z "$rel" ] && continue local h1 h2 # For keychain.1 apply normalization (skip first line) before comparing to avoid Pod::Man header diffs. if [ "$(basename "$rel")" = "keychain.1" ]; then h1=$(tail -n +2 "$tmp_local/$root/$rel" | sha256sum | awk '{print $1}') h2=$(tail -n +2 "$tmp_ci/$root/$rel" | sha256sum | awk '{print $1}') if [ "$h1" != "$h2" ]; then echo " keychain-$VER.tar.gz: content mismatch in $rel (beyond header)" >&2 echo "--- LOCAL: $rel" >&2 echo "+++ CI: $rel" >&2 diff -u <(tail -n +2 "$tmp_local/$root/$rel") <(tail -n +2 "$tmp_ci/$root/$rel") | head -20 >&2 mismatch=1 fi else h1=$(sha256sum "$tmp_local/$root/$rel" | awk '{print $1}') h2=$(sha256sum "$tmp_ci/$root/$rel" | awk '{print $1}') if [ "$h1" != "$h2" ]; then echo " keychain-$VER.tar.gz: content mismatch in $rel" >&2 echo "--- LOCAL: $rel" >&2 echo "+++ CI: $rel" >&2 diff -u "$tmp_local/$root/$rel" "$tmp_ci/$root/$rel" | head -20 >&2 mismatch=1 fi fi done </dev/null 2>&1; then printf ' %-20s (normalized match ignoring Pod::Man header)\n' "$basename_artifact" else printf ' %-20s LOCAL %s != CI %s *DIFF* (content mismatch beyond header)\n' "$basename_artifact" "$L" "$R" diff_flag=1 fi fi ;; "keychain-$VER.tar.gz") if compare_tar_content "$artifact" "$ci_artifact_path"; then # If tar blob hash matches display it; else note normalized match. L=$(calc_sha256 "$artifact"); R=$(calc_sha256 "$ci_artifact_path") if [ "$L" = "$R" ]; then printf ' %-20s %s (match)\n' "$basename_artifact" "$L" else printf ' %-20s (content match; tar/gzip metadata differ)\n' "$basename_artifact" fi else printf ' %-20s *CONTENT DIFF* (see above messages)\n' "$basename_artifact" diff_flag=1 fi ;; esac done if [ $diff_flag -ne 0 ]; then echo echo "Artifact mismatch detected between LOCAL build and CI (Debian) build." >&2 echo "Release aborted (provenance mismatch) unless KEYCHAIN_FORCE_LOCAL=1 is set." >&2 echo echo "Copy/paste diff commands:" >&2 echo " VER=$VER; CI_DIR=$CI_DIR" >&2 echo " diff -u keychain \"$CI_DIR/keychain\"" >&2 echo " diff -u keychain.1 \"$CI_DIR/keychain.1\"" >&2 echo " diff -u <(tar tzf dist/keychain-$VER.tar.gz | sort) <(tar tzf $CI_DIR/dist/keychain-$VER.tar.gz | sort)" >&2 echo " mkdir -p /tmp/kc-local /tmp/kc-ci && tar xzf dist/keychain-$VER.tar.gz -C /tmp/kc-local && tar xzf $CI_DIR/dist/keychain-$VER.tar.gz -C /tmp/kc-ci && diff -ru /tmp/kc-local/keychain-$VER /tmp/kc-ci/keychain-$VER" >&2 echo if [ "${KEYCHAIN_FORCE_LOCAL:-}" = 1 ]; then echo "KEYCHAIN_FORCE_LOCAL=1 set: proceeding using LOCAL artifacts despite mismatches." >&2 else exit 1 fi fi # Decide which artifact paths to publish (never overwrite local originals) if [ "${KEYCHAIN_FORCE_LOCAL:-}" = 1 ]; then KEYCHAIN_ASSET_KEYCHAIN="keychain" KEYCHAIN_ASSET_MAN="keychain.1" KEYCHAIN_ASSET_TARBALL="dist/keychain-$VER.tar.gz" echo "Source selection: USING LOCAL artifacts (override)." >&2 else # All artifacts matched (raw or normalized) -> use CI versions KEYCHAIN_ASSET_KEYCHAIN="$CI_DIR/keychain" KEYCHAIN_ASSET_MAN="$CI_DIR/keychain.1" KEYCHAIN_ASSET_TARBALL="$CI_DIR/dist/keychain-$VER.tar.gz" echo "Source selection: USING CI artifacts (canonical)." >&2 fi export KEYCHAIN_ASSET_KEYCHAIN KEYCHAIN_ASSET_MAN KEYCHAIN_ASSET_TARBALL # 3. Generate full release notes (ChangeLog excerpt + provenance) for preview NOTES_FILE=$(mktemp) ./scripts/release-notes.sh "$VER" "$NOTES_FILE" || { echo "Failed to generate release notes preview" >&2; exit 1; } echo echo "================ Release Notes Preview (generated) ======================" sed 's/^/| /' "$NOTES_FILE" echo "=========================================================================" printf 'Continue with %s of %s? (Y/N): ' "$MODE" "$VER" read -r ans < /dev/tty || ans=N case "$ans" in Y|y) echo "Continuing...";; *) echo "Aborted by user."; exit 1;; esac # 4. Publish / refresh if [ "$MODE" = create ]; then ./scripts/release-create.sh "$VER" else ./scripts/release-refresh.sh "$VER" fi echo "Done." keychain-2.9.8/scripts/release-refresh.sh000077500000000000000000000050611510172470100204270ustar00rootroot00000000000000#!/bin/sh # Refresh (update) assets of an existing release. If the release does not exist, fail. set -eu VER=${1:?usage: release-refresh.sh } GITHUB_REPOSITORY=${GITHUB_REPOSITORY:-danielrobbins/keychain} . "$(dirname "$0")/release-common.sh" [ "$(cat VERSION)" = "$VER" ] || fail "VERSION file mismatch ($(cat VERSION)" != "$VER)" rel_json=$(curl -fsSL -H "Authorization: Bearer $GITHUB_TOKEN" https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/tags/$VER || true) [ -n "$rel_json" ] || fail "Release for tag $VER not found" rel_id=$(printf '%s' "$rel_json" | jq '.id') [ "$rel_id" != "null" ] || fail "Could not determine release id" echo "Deleting existing assets..." printf '%s' "$rel_json" | jq -r '.assets[].id' | while read -r aid; do [ -n "$aid" ] || continue curl -fsSL -X DELETE -H "Authorization: Bearer $GITHUB_TOKEN" https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/assets/$aid >/dev/null || fail "Failed to delete asset $aid" echo " deleted asset id $aid" done echo "Uploading replacement assets..." ASSET_KEYCHAIN=${KEYCHAIN_ASSET_KEYCHAIN:-keychain} ASSET_MAN=${KEYCHAIN_ASSET_MAN:-keychain.1} ASSET_TARBALL=${KEYCHAIN_ASSET_TARBALL:-dist/keychain-$VER.tar.gz} # (Note: By default we do not modify existing release notes. Set KEYCHAIN_UPDATE_NOTES=1 to rebuild.) for f in "$ASSET_TARBALL" "$ASSET_KEYCHAIN" "$ASSET_MAN"; do [ -f "$f" ] || fail "Missing asset file $f" case $(basename "$f") in "keychain-$VER.tar.gz") pname="keychain-$VER.tar.gz";; keychain) pname="keychain";; keychain.1) pname="keychain.1";; *) if echo "$f" | grep -q "keychain-$VER.tar.gz"; then pname="keychain-$VER.tar.gz"; fi if echo "$f" | grep -q "/keychain$"; then pname="keychain"; fi if echo "$f" | grep -q "/keychain.1$"; then pname="keychain.1"; fi [ -n "${pname:-}" ] || fail "Could not determine asset publish name for $f";; esac curl -sS -X POST \ -H "Authorization: Bearer $GITHUB_TOKEN" \ -H "Content-Type: application/octet-stream" \ --data-binary @"$f" \ "https://uploads.github.com/repos/${GITHUB_REPOSITORY}/releases/$rel_id/assets?name=$pname" >/dev/null echo " uploaded $pname (from $f)" done echo "Regenerating release notes (including provenance) ..." tmp_notes=$(mktemp) ./scripts/release-notes.sh "$VER" "$tmp_notes" patch_json=$(mktemp) printf '{"body": %s}\n' "$(jq -Rs . < "$tmp_notes")" > "$patch_json" api PATCH /releases/$rel_id "$patch_json" >/dev/null || echo "Warning: failed to PATCH release body" >&2 rm -f "$tmp_notes" "$patch_json" echo "Assets and notes refreshed for release $VER." keychain-2.9.8/scripts/test-completion.sh000066400000000000000000000055401510172470100205000ustar00rootroot00000000000000#!/bin/bash # Test script for bash completion functionality # Tests that completion works correctly across different environments # Returns 0 on success, 1 on any failure # Usage: ./scripts/test-completion.sh set -u SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" EXIT_CODE=0 echo "=== Bash Completion Test ===" echo # Source the completion script echo "1. Sourcing completion script..." if ! source "$REPO_DIR/completions/keychain.bash" 2>/dev/null; then echo " ERROR: Failed to source completion script" exit 1 fi echo " Done" echo # Test if __keychain_command_line_options works echo "2. Testing __keychain_command_line_options function..." opts_output=$(__keychain_command_line_options) if [ -z "$opts_output" ]; then echo " ERROR: Function returned empty string" echo " (This usually means 'keychain' is not in PATH or is keychain.sh instead of generated keychain)" EXIT_CODE=1 else echo " SUCCESS: Function returned options" echo " First 5 options: $(echo "$opts_output" | awk '{for(i=1;i<=5;i++) print $i}' | tr '\n' ' ')" fi echo # Test the array form echo "3. Testing options as array..." # shellcheck disable=SC2207 opts_array=( $(__keychain_command_line_options) ) echo " Array has ${#opts_array[@]} elements" if [ ${#opts_array[@]} -eq 0 ]; then echo " ERROR: Array is empty" EXIT_CODE=1 else echo " SUCCESS: First 5 elements: ${opts_array[*]:0:5}" fi echo # Simulate completion for "keychain -" echo "4. Simulating completion for 'keychain -'..." COMP_WORDS=(keychain -) COMP_CWORD=1 _keychain echo " COMPREPLY has ${#COMPREPLY[@]} items" if [ ${#COMPREPLY[@]} -eq 0 ]; then echo " ERROR: No completions returned" EXIT_CODE=1 else echo " SUCCESS: First 5 completions:" for i in "${COMPREPLY[@]:0:5}"; do echo " - $i" done fi echo # Check which keychain is being found echo "5. Checking keychain executable location..." if command -v keychain >/dev/null 2>&1; then echo " Found: $(command -v keychain)" elif [ -x "$REPO_DIR/keychain" ]; then echo " Found: $REPO_DIR/keychain (local)" elif [ -x "$REPO_DIR/keychain.sh" ]; then echo " WARNING: Only found keychain.sh (needs 'make' to generate full keychain)" else echo " WARNING: keychain not found in PATH or local directory" fi echo # Test calling keychain --help echo "6. Testing 'keychain --help' output..." if keychain --help >/dev/null 2>&1; then echo " SUCCESS: keychain --help works" first_line=$(keychain --help 2>&1 | head -1) if [ -n "$first_line" ]; then echo " First line: $first_line" fi else echo " ERROR: keychain --help failed" EXIT_CODE=1 fi echo echo "=== Test Complete ===" if [ $EXIT_CODE -eq 0 ]; then echo "Result: SUCCESS - All tests passed" else echo "Result: FAILURE - Some tests failed" fi exit $EXIT_CODE keychain-2.9.8/scripts/test-space-home.sh000077500000000000000000000052541510172470100203550ustar00rootroot00000000000000#!/bin/sh # Minimal harness to simulate a HOME with spaces and exercise keychain behaviors # Usage: scripts/test-space-home.sh [optional-extra-key] # It creates a temp directory with a space, sets HOME, prepares dummy ssh keys, # runs keychain, and reports whether pidfiles and ssh-add succeeded. # Returns 0 on success, non-zero on failure. set -eu VER=${1:-test} EXTRA_KEY=${2:-} FAILED=0 WORKBASE=$(mktemp -d) SPACE_HOME="${WORKBASE}/User Space" mkdir -p "${SPACE_HOME}/.ssh" "${SPACE_HOME}/bin" cp keychain.sh "${SPACE_HOME}/bin/keychain" 2>/dev/null || cp ./keychain.sh "${SPACE_HOME}/bin/keychain" chmod 700 "${SPACE_HOME}/.ssh" # Generate minimal throwaway key if ssh-keygen available if command -v ssh-keygen >/dev/null 2>&1; then ssh-keygen -t ed25519 -N '' -f "${SPACE_HOME}/.ssh/id_ed25519" >/dev/null 2>&1 || true fi # Optional additional key copy if [ -n "$EXTRA_KEY" ] && [ -f "$EXTRA_KEY" ]; then cp "$EXTRA_KEY" "${SPACE_HOME}/.ssh/" 2>/dev/null || true fi export HOME="${SPACE_HOME}" export PATH="${SPACE_HOME}/bin:${PATH}" printf '\n[info] Simulated HOME with space: %s\n' "$HOME" printf '[info] Running keychain (version stub %s) ...\n' "$VER" # Basic invocation: adopt or spawn then load one key if output=$("${SPACE_HOME}/bin/keychain" -q --eval id_ed25519 2>&1); then echo "$output" | sed 's/^/[keychain] /' else echo "$output" | sed 's/^/[keychain-err] /' >&2 echo "ERROR: keychain command failed" >&2 FAILED=1 fi # Inspect pidfiles PIDBASE="${HOME}/.keychain/$(uname -n 2>/dev/null || echo host)" for suffix in -sh -csh -fish; do f="${PIDBASE}${suffix}" if [ -f "$f" ]; then echo "[pidfile] Found $f"; head -n 2 "$f" | sed 's/^/[pidfile] /' else echo "ERROR: pidfile MISSING: $f" >&2 FAILED=1 fi done # Verify that SSH_AUTH_SOCK has not been truncated if eval "$(cat "${PIDBASE}-sh" 2>/dev/null || echo true)"; then case "$SSH_AUTH_SOCK" in *" "*) echo "ERROR: SSH_AUTH_SOCK contains a space unexpectedly: $SSH_AUTH_SOCK" >&2 FAILED=1 ;; *) if [ -S "$SSH_AUTH_SOCK" ]; then echo "[ok] SSH_AUTH_SOCK socket exists" else echo "ERROR: SSH_AUTH_SOCK path not a socket: $SSH_AUTH_SOCK" >&2 FAILED=1 fi ;; esac else echo "ERROR: Unable to eval sh pidfile" >&2 FAILED=1 fi # Attempt ssh-add -l to confirm agent access if ssh-add -l >/dev/null 2>&1; then echo "[ok] ssh-add -l succeeded" else echo "ERROR: ssh-add -l failed" >&2 FAILED=1 fi # Cleanup summary (keep workspace for inspection) - comment out to retain # rm -rf "$WORKBASE" if [ $FAILED -eq 0 ]; then echo "[done] All tests PASSED" exit 0 else echo "[done] Tests FAILED - see errors above" >&2 exit 1 fi