diff --git a/docs/contributing/commit-signing.mdx b/docs/contributing/commit-signing.mdx index 1ca6e98ec..9457c2f86 100644 --- a/docs/contributing/commit-signing.mdx +++ b/docs/contributing/commit-signing.mdx @@ -2,337 +2,379 @@ sidebar_position: 3 --- -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; - # Commit signing -It's possible to configure git with any name and email, enabling bad actors to spoof commits and -impersonate whomever they want. GitHub supports several ways to digitally sign git commits, -verifying that they came from someone with access to a previously configured private key. - -For example, on 3 August 2022, Stephen Lacy -[shared on Twitter](https://twitter.com/stephenlacy/status/1554697080718823424) how he uncovered a -massive malware attack on GitHub by noticing unverified commits (i.e. commits that were not -digitally signed). - -To protect against commit spoofing, all Bitwarden contributors are encouraged to digitally -sign their commits. +Git allows anyone to configure any name and email on their commits, which means bad actors can spoof +commits and impersonate other contributors. Digitally signing commits proves they came from someone +with access to a specific private key. -## Setting up commit signing +To protect against commit spoofing, **all Bitwarden contributors are encouraged to digitally sign +their commits**. -GitHub supports [commit signing][github-verification] with SSH, GPG, and S/MIME. +## Choosing a signing method -:::tip +GitHub supports [commit signing][github-verification] with SSH, GPG, and S/MIME. This guide covers +SSH, which GitHub recommends as [the simplest approach for individual users][github-verification]. -If you're unsure what to use, we recommend you create a commit signing key using SSH per -latest security best practices (see the -[PGP problem](https://www.latacora.com/blog/2019/07/16/the-pgp-problem/) for more details). For -maximum security, consider using a [hardware-backed SSH key](#hardware-backed-ssh-key-configuration) -(YubiKey or other FIDO2 device) as your signing key. - -::: +There are three ways to set up SSH commit signing, in order of recommendation: -### Standard SSH key configuration +1. **[Hardware-backed SSH key](#hardware-backed-ssh-key)**: private key lives on a FIDO2 security + key (e.g., YubiKey 5+). Each commit requires a PIN and a physical touch. This is the most secure + option. +2. **[Standard SSH key](#standard-ssh-key)**: private key lives on disk, protected by a passphrase. + Simpler to set up, but the key material is extractable. +3. **[Bitwarden SSH agent](#bitwarden-ssh-agent)**: import an SSH key into Bitwarden Desktop and use + the built-in SSH agent. Keys are available while your vault is unlocked. -1. Ensure you have globally configured your git email address: +Pick one path below, then continue to [Add the key to GitHub](#add-the-key-to-github). - ```bash - git config --global user.email "you@example.com" - ``` +## Hardware-backed SSH key -2. Generate an SSH key for commit signing: +Hardware-backed keys using FIDO2 security keys provide the strongest protection. Private keys are +generated on and never leave the hardware, and every signing operation requires physical interaction +with the device. - ```bash - # path to save your private signing key - KEY_FILE=~/.ssh/bw-signing - ssh-keygen -f $KEY_FILE -C "$(git config --global --get user.email)" -t ed25519 - ``` +### Prerequisites - :::tip +- macOS (Apple Silicon) with Homebrew +- A FIDO2-compatible security key (must support ed25519-sk; older U2F-only keys only support + ecdsa-sk) - Remember to protect the key with a [strong passphrase or password][password-generation]. - Alternatively set up - [a hardware key as your signing key](#hardware-backed-ssh-key-configuration). +### Install dependencies - ::: +The system OpenSSH (8.2+) technically supports ed25519-sk, but lacks the FIDO middleware needed to +talk to hardware keys. Install Homebrew's OpenSSH, which has FIDO support compiled in, along with +`libfido2`: -3. Configure git to sign using SSH. +```bash +brew install openssh libfido2 +``` - ```bash - git config --global gpg.format ssh - git config --global user.signingkey "${KEY_FILE}.pub" - ``` +Ensure Homebrew's binaries take precedence. Add to `~/.zshrc` if not already present: -4. Follow the - [SSH GitHub documentation](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification#ssh-commit-signature-verification) - to configure commit signing +```bash +export PATH="/opt/homebrew/bin:$PATH" +``` -5. Configure your preferred git tool below ([CLI](#commit-signing-with-git-cli), - [VS Code](#commit-signing-with-visual-studio-code), - [SourceTree](#commit-signing-with-sourcetree)) +Restart your shell (open a new terminal tab, or run `source ~/.zshrc`) and verify the correct binary +is active: -6. Push a test commit to GitHub and ensure that the "Verified" badge appears next to the commit - description: +```bash +which ssh-keygen # should print /opt/homebrew/bin/ssh-keygen +``` - ![Image showing the Verified badge in GitHub](./commit-signing.png) +If it points to `/usr/bin/`, your PATH isn't set correctly. Fix that before continuing; the system +binary will silently fail at later steps. -7. (Optional) See - [Store your passphrase in your OS keychain](#store-your-passphrase-in-your-os-keychain) below to - avoid being prompted for your SSH key passphrase every time you sign a commit. +### Set a FIDO2 PIN -### Use Bitwarden SSH agent +If you haven't already, set a PIN on your security key: -Import your SSH key into Bitwarden desktop. Then, follow our -[guide](https://bitwarden.com/help/ssh-agent/#configure-git-for-ssh-signing) on configuring the -desktop app for SSH authentication and Git commit signing. This ensures your SSH keys are available -while your vault is unlocked. +```bash +brew install ykman +ykman fido access change-pin +``` -### Store your passphrase in your OS keychain +### Generate the key -#### macOS +Ensure your git email is configured, then plug in your security key and generate the key: -On macOS, you can use the built-in Keychain to store your SSH key passphrase so you don't have to -enter it every time you sign a commit. +```bash +git config --global user.email "you@example.com" +ssh-keygen -t ed25519-sk -O verify-required -C "$(git config --global --get user.email)" +``` -1. Add your SSH key to the ssh-agent and store your passphrase in the Keychain: +`-O verify-required` ensures every SSH operation requires both a PIN and a physical touch. You'll be +prompted to touch the key during generation. The passphrase prompt is optional, since the hardware +key itself is the primary security factor. - ```bash - ssh-add --apple-use-keychain ~/.ssh/bw-signing - ssh-add -l # verify the key was added - ``` +To create a **resident key** (stored on the hardware, portable across machines via `ssh-keygen -K`), +add `-O resident`: -2. The above command will need to be run each time you restart your computer. If you would like to - automate this process, then you can leverage - [LaunchAgents](https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html#//apple_ref/doc/uid/10000172i-SW7-BCIEDDBJ) - to run the command on startup. +```bash +ssh-keygen -t ed25519-sk -O resident -O verify-required -C "$(git config --global --get user.email)" +``` - a. Ensure the LaunchAgents directory exists: +Confirm the key was created: - ```bash - mkdir -p ~/Library/LaunchAgents - ``` +```bash +ls -la ~/.ssh/id_ed25519_sk* +``` - b. Create a new plist file for the LaunchAgent: +You should see both `id_ed25519_sk` (private key handle) and `id_ed25519_sk.pub` (public key). - ```bash - USER_NAME=$(whoami) +### Configure SSH - cat > ~/Library/LaunchAgents/com.ssh-add-bw-signing.plist < +``` +IgnoreUnknown UseKeychain,AddKeysToAgent - +Host github.com + IdentityFile ~/.ssh/id_ed25519_sk + IdentityAgent none +``` - - - Label - com.ssh-add-bw-signing - ProgramArguments - - /usr/bin/ssh-add - --apple-use-keychain - /Users/${USER_NAME}/.ssh/bw-signing - - RunAtLoad - - - - PLIST - ``` +`IgnoreUnknown` lets this config work with both the system SSH (which supports Apple-specific +options like `UseKeychain`) and Homebrew's SSH (which doesn't). `IdentityAgent none` bypasses the +macOS SSH agent, which can cache the FIDO key handle and then fail on subsequent auth attempts. +Disabling it forces SSH to talk to the YubiKey directly every time, which is the correct behavior +for `verify-required` keys. +### Configure Git - c. Validate that your plist is valid: +```bash +git config --global gpg.format ssh +git config --global user.signingkey ~/.ssh/id_ed25519_sk.pub +git config --global commit.gpgSign true +git config --global tag.gpgSign true +``` - ```bash - plutil -lint ~/Library/LaunchAgents/com.ssh-add-bw-signing.plist - # you should see: "OK" - ``` +Set up an allowed signers file for local signature verification. This snippet is idempotent, so it's +safe to run more than once: - d. Configure `launchctl` to enable the new LaunchAgent for your next restart: +```bash +SIGNER_ENTRY="$(git config user.email) $(cat ~/.ssh/id_ed25519_sk.pub)" +touch ~/.ssh/allowed_signers +grep -qF "$SIGNER_ENTRY" ~/.ssh/allowed_signers || echo "$SIGNER_ENTRY" >> ~/.ssh/allowed_signers +git config --global gpg.ssh.allowedSignersFile ~/.ssh/allowed_signers +``` - ```bash - launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.ssh-add-bw-signing.plist - launchctl enable gui/$(id -u)/com.ssh-add-bw-signing - ``` +#### Fix the PIN prompt for commit signing - e. You can kickstart the LaunchAgent and confirm it will work by running (only works if you haven't loaded your key yet): +Git calls `ssh-keygen -Y sign` internally without connecting it to your terminal, so the PIN prompt +gets swallowed and the commit appears to hang. The fix is a wrapper script that redirects +stdin/stderr to `/dev/tty`: - ```bash - launchctl kickstart -k gui/$(id -u)/com.ssh-add-bw-signing - ssh-add -l # verify the key was added - ``` +```bash +cat > ~/.ssh/ssh-keygen-sign-wrapper <<'EOF' +#!/bin/bash +exec /opt/homebrew/bin/ssh-keygen "$@" < /dev/tty 2> /dev/tty +EOF +chmod +x ~/.ssh/ssh-keygen-sign-wrapper - f. Restart your computer to verify that the LaunchAgent works as expected. After restarting, run `ssh-add -l` to verify that your key was added to the ssh-agent - automatically. +git config --global gpg.ssh.program ~/.ssh/ssh-keygen-sign-wrapper +``` -### Hardware-backed SSH key configuration +Now continue to [Add the key to GitHub](#add-the-key-to-github). -For contributors who want maximum security, hardware-backed SSH keys using FIDO2/U2F security keys -(like YubiKeys) provide superior protection by storing private keys on hardware that cannot be -extracted or copied. +### Fallback strategy -#### Benefits of hardware-backed keys +:::caution -- **Physical presence required**: Each signing operation requires touching the hardware key -- **Locked private keys**: Private keys are generated on and never leave the hardware -- **Tamper-resistant**: Hardware provides protection against physical and software attacks -- **Multi-factor authentication**: Combines something you have (the key) with something you know - (PIN) +Make sure you have a recovery plan in case you lose your hardware key: -#### Setting up sk-ed25519 keys +- Register multiple hardware keys with GitHub +- Keep a traditional SSH key as a secure backup for emergencies +- If using resident keys, back up the key handle with `ssh-keygen -K` on a second machine -:::note macOS Users +::: -On macOS, you'll need to install OpenSSH via Homebrew as the system's built-in OpenSSH lacks proper -FIDO2 support: +## Standard SSH key -```bash -brew install openssh -``` +A standard ed25519 SSH key stored on disk, protected by a passphrase. -After installation, restart your terminal to ensure the Homebrew version is used. If it's not then -fully qualify the path to the homebrew version of ssh-keygen in the example, like -`/opt/homebrew/bin/ssh-keygen [everything else]`. +1. Ensure your git email is configured: -::: + ```bash + git config --global user.email "you@example.com" + ``` -1. Generate an sk-ed25519 key for commit signing: +2. Generate an SSH key for commit signing: ```bash - # Generate hardware-backed signing key - ssh-keygen -t ed25519-sk -f ~/.ssh/bw-signing-sk -C "$(git config --global --get user.email)" + ssh-keygen -t ed25519 -f ~/.ssh/bw-signing -C "$(git config --global --get user.email)" ``` - :::note + :::tip - You'll need to touch your security key when prompted during key generation and each time you sign - a commit. + Protect the key with a [strong passphrase][password-generation]. For stronger protection, + consider a [hardware-backed key](#hardware-backed-ssh-key) instead. ::: -2. Configure git to use your hardware-backed key: +3. Configure Git to sign using SSH: ```bash git config --global gpg.format ssh - git config --global user.signingkey ~/.ssh/bw-signing-sk.pub + git config --global user.signingkey ~/.ssh/bw-signing.pub git config --global commit.gpgSign true git config --global tag.gpgSign true ``` -3. Add the public key to your GitHub account following the [GitHub - documentation][github-verification] +4. (Optional) Store your passphrase in the macOS Keychain so you don't have to enter it on every + commit. See + [Store your passphrase in the macOS Keychain](#store-your-passphrase-in-the-macos-keychain). -#### Hardware key requirements +Now continue to [Add the key to GitHub](#add-the-key-to-github). -- **FIDO2/U2F compatible security key** (YubiKey 5 series, SoloKeys, etc.) -- **OpenSSH 8.2+** with FIDO2 support (on macOS, install via Homebrew: `brew install openssh`) -- **Physical access** to the security key for each signing operation +## Bitwarden SSH agent -#### Fallback strategy +Import your SSH key into Bitwarden Desktop, then follow the +[Bitwarden SSH agent guide](https://bitwarden.com/help/ssh-agent/#configure-git-for-ssh-signing) to +configure the desktop app for SSH authentication and Git commit signing. Your SSH keys will be +available while your vault is unlocked. -:::caution +After completing that guide, continue to [Add the key to GitHub](#add-the-key-to-github). -When going the hardware key route it is important to make sure you have the infrastructure set up to -recover from the loss of a hardware key. At least one backup plan should be set up. Some ideas: +## Add the key to GitHub -- Create and configure multiple hardware keys -- Generate both a hardware-backed key and a traditional SSH key: - - Use the hardware-backed key for day-to-day development - - Keep a traditional SSH key as a secure backup for emergency situations +Copy your public key (adjust the path if you used a custom filename): -::: +```bash +# Hardware-backed key +pbcopy < ~/.ssh/id_ed25519_sk.pub -### Commit signing with git CLI +# Standard key +pbcopy < ~/.ssh/bw-signing.pub +``` -- After configuring commit signing, you can sign a commit by using the `-S` flag: +In **GitHub → Settings → SSH and GPG keys → New SSH key**, add the same public key twice: once as an +**Authentication Key** and once as a **Signing Key**. - ```bash - git commit -S - ``` +### Test authentication -- To avoid using the `-S` flag every time, you can sign all commits and tags by default: +```bash +ssh -T git@github.com +``` - ```bash - git config --global commit.gpgSign true - git config --global tag.gpgSign true - ``` +You should see: -### Commit signing with Visual Studio Code +``` +Hi ! You've successfully authenticated, but GitHub does not provide shell access. +``` -1. Open **Preferences** → **Settings** -2. Search for **“commit signing”** -3. Enable commit signing +For hardware keys, you'll be prompted for your FIDO2 PIN and a physical touch. If this doesn't work, +stop here and troubleshoot before continuing. - ![Image showing the settings in VS Code](./vscode-preferences-settings-commit-signing.png) +### Verify commit signing -
- macOS: GPG Key Passphrase Prompt Issue +```bash +git commit --allow-empty -m "test signed commit" +git log --show-signature -1 +``` -Some macOS users have had issues with VS Code and the gpg-agent not prompting for the GPG Key -Passphrase in order to sign commits when using the VS Code git GUI. This is illustrated by VS Code -displaying an error popup message: `Git: gpg failed to sign the data`. +`git log` should show "Good signature." Push the commit to GitHub and confirm the **Verified** badge +appears next to the commit. -A [workaround](https://github.com/microsoft/vscode/issues/43809#issuecomment-828773909) for this -issue is to configure your gpg-agent to use -[pinentry](https://www.gnupg.org/related_software/pinentry/index.html) for macOS in order to force a -secure prompt. Run the following in a terminal of your choice: +## Git client setup -1. Install pinentry-mac: +### VS Code - ```bash - brew install pinentry-mac - ``` +1. Open **Preferences → Settings** +2. Search for **"commit signing"** +3. Enable **Git: Enable Commit Signing** -2. Configure gpg-agent to use pinentry-mac: +### SourceTree + +Refer to Atlassian's guide: +[Setup GPG to sign commits within SourceTree](https://confluence.atlassian.com/sourcetreekb/setup-gpg-to-sign-commits-within-sourcetree-765397791.html). + +## What to expect day-to-day + +**Hardware keys:** Every `git push` and `git commit` will prompt for your FIDO2 PIN and a physical +touch. This is by design; no one can push or sign commits with your identity without physical access +to your security key and knowledge of the PIN. The prompts become second nature quickly, but expect +each operation to take a couple of extra seconds. + +**Standard SSH keys:** If you stored your passphrase in the macOS Keychain, commits and pushes will +work without any prompts. Otherwise, you'll be prompted for your passphrase when the key is first +used in a session. + +## Store your passphrase in the macOS Keychain + +This section applies to **standard SSH keys only** (not hardware-backed keys, which require a touch +on every operation by design). + +Add your SSH key to the ssh-agent and store your passphrase in the Keychain: + +```bash +ssh-add --apple-use-keychain ~/.ssh/bw-signing +ssh-add -l # verify the key was added +``` + +This needs to be re-run after every restart. To automate it, create a LaunchAgent: + +1. Create the LaunchAgents directory if it doesn't exist: ```bash - echo "pinentry-program $(which pinentry-mac)" >> ~/.gnupg/gpg-agent.conf + mkdir -p ~/Library/LaunchAgents ``` -3. Restart gpg-agent: +2. Create the plist file: ```bash - killall gpg-agent + USER_NAME=$(whoami) + + cat > ~/Library/LaunchAgents/com.ssh-add-bw-signing.plist < + + + + Label + com.ssh-add-bw-signing + ProgramArguments + + /usr/bin/ssh-add + --apple-use-keychain + /Users/${USER_NAME}/.ssh/bw-signing + + RunAtLoad + + + + PLIST ``` -**Note**: Note: you might have to restart VS Code for this to take effect, but you should now be -prompted for your GPG Key Passphrase as needed. If this does not solve your issue, please follow the -[GPG Key Troubleshooting](#gpg-key-troubleshooting) guide below. +3. Validate, enable, and test: -
- -### Commit signing with SourceTree + ```bash + plutil -lint ~/Library/LaunchAgents/com.ssh-add-bw-signing.plist -Refer to -[Setup GPG to sign commits within SourceTree](https://confluence.atlassian.com/sourcetreekb/setup-gpg-to-sign-commits-within-sourcetree-765397791.html). + launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.ssh-add-bw-signing.plist + launchctl enable gui/$(id -u)/com.ssh-add-bw-signing -## GPG Key Troubleshooting + # Kick-start it now (only works if the key isn't loaded yet) + launchctl kickstart -k gui/$(id -u)/com.ssh-add-bw-signing + ssh-add -l # verify the key was added + ``` -If you see the following error: +4. Restart your computer and run `ssh-add -l` to confirm the key loads automatically. -```text -error: gpg failed to sign the data -``` +## Cleanup -Make sure you’ve added the following to your shell config: +Remove any old SSH keys from GitHub that are no longer in use. Compare the SHA256 fingerprints shown +in GitHub's UI against your local keys: ```bash -export GPG_TTY=$(tty) +for f in ~/.ssh/*.pub; do echo "=== $f ==="; ssh-keygen -l -f "$f"; done ``` -- For **zsh**, add it to `~/.zshrc` -- For **bash**, add it to `~/.bashrc` +## Troubleshooting + +### Hardware key issues + +**Commit hangs with no PIN prompt.** Git is using the system `ssh-keygen` instead of Homebrew's. Run +`which ssh-keygen`; it should print `/opt/homebrew/bin/ssh-keygen`. If not, fix your PATH and +re-source your shell. Also confirm the wrapper script at `~/.ssh/ssh-keygen-sign-wrapper` exists and +is executable. -After updating the file, restart your terminal for the change to take effect. +**`ssh -T git@github.com` returns "Permission denied."** Check that `~/.ssh/config` has the correct +`IdentityFile` path. Run `ssh -vT git@github.com` to see which keys SSH is offering; the verbose +output will show whether your FIDO key is being tried. -
- More help with this error +**"Key enrollment failed" or no touch prompt during key generation.** The YubiKey may not be +recognized. Try unplugging and re-plugging it, then run `ykman fido info` to confirm the FIDO2 +application is accessible. If `ykman` can't see it, try a different USB port or check for +interfering processes (`gpg-agent`, `yubikey-agent`). -See this -[troubleshooting guide](https://gist.github.com/paolocarrasco/18ca8fe6e63490ae1be23e84a7039374). +**Authentication works but suddenly stops.** The macOS SSH agent may have cached a stale FIDO key +handle. Confirm `IdentityAgent none` is set in `~/.ssh/config` for the `github.com` host. If the +problem persists, clear the agent with `ssh-add -D` and retry. -
+**YubiKey PIN locked out.** After 8 consecutive wrong PIN attempts, the FIDO2 PIN locks. Recovery +requires a full FIDO reset (`ykman fido reset`), which wipes all resident credentials on the key. +You'll need to generate a new key pair and re-register it on GitHub. [password-generation]: https://bitwarden.com/help/generator/#password-types [github-verification]: