(setq plaintext 'everywhere)



Replace SSH with GnuPG

Table of Contents

[2024-12-13 Fri]

Below describes the process of using the gpg-agent as a drop-in replacement for the ssh-agent. It also has steps for importing your existing ssh keys into gpg. Reason for wanting to do this are:

1 GPG as a ssh-agent

To make gpg act as a ssh-agent add these lines to your ~/.bashrc:

export GPG_TTY=$(tty)
unset SSH_AGENT_PID
[ "${gnupg_SSH_AUTH_SOCK_by:-0}" -ne $$ ] \
    && export SSH_AUTH_SOCK="$(gpgconf --list-dirs agent-ssh-socket)"

It may or may not be necessary to enable ssh support in your ~/.gnupg/gpg-agent.conf:

echo 'enable-ssh-support' >> ~/.gnupg/gpg-agent.conf

2 TODO Import Existing SSH Keys

If you already have SSH keys you can import them into GnuPG. Perhaps I will make some blog post about this in the future. For now you can follow these guides 1 and 2, depending on the type of SSH key you have.

3 Create SSH Keys with GPG

The easiest way to start using GPG as a replacement for SSH is to create new SSH keys. We will do this by adding an authentication subkey to an already existing GPG key. If you don't already have a GPG key, create one with:

gpg --full-gen-key

and follow the instructions. If you need help with generating a key pair read3.

  1. To create an authentication subkey:

    gpg --expert --edit-key <your-id>
    
  2. Enter addkey.
  3. Choose RSA (set your own capabilities) (maybe 8).
  4. The allowed actions of the subkey should be authenticate and nothing else.
  5. The size of the key should be at least 2048, for the key to be considered secure for the foreseeable future.
  6. Choose an expiration date you like.
  7. Enter quit.

4 Export GPG Authentication Keys as SSH Public Keys

For every authentication key you want to use for ssh you need to add their public key to ~/.ssh so you can reference them in ~/.ssh/config. You also need to add the gpg subkey fingerprints to ~/.gnupg/sshcontrol so the gpg-agent knows which keys can be used for ssh.

# Ensure the directory exists
mkdir ~/.ssh

# Select the fingerprint of a authentication subkey
gpg -K --with-subkey-fingerprint

# Note the ! after the fingerprint
# Note this will overwrite ~/.ssh/<public-key> if it exists
gpg --output ~/.ssh/<public-key> --export-ssh-key <fingerprint>!

# Modify permission on the public key file to 600
chmod 0600 ~/.ssh/<public-key>

# Add keygrips of auth subkeys to ~/.gnupg/sshcontrol
gpg -K --with-keygrip
echo <key-grip> >> ~/.gnupg/sshcontrol

4.1 Script

If you are too lazy to enter all the above commands into the terminal then you can this script.

set -e

confirm_info () {
    gpg -K --with-subkey-fingerprint --with-keygrip "${uid}"
    cat << EOF
   Fingerprint:      ${fingerprint}
   Keygrip:          ${keygrip}
   Public key file:  ${file_pk}
Is this correct (y/N):
EOF
    read -r confirm
    [ "${confirm}" = y ] && return 0
    return 1
}

mkdir -p ~/.ssh
while true; do
    gpg -K
    cat << EOF
Which uid to export as SSH key, this will also be used as the filename
of the public key in ~/.ssh (or "q" to finish): '
EOF
    read -r uid
    [ "${uid}" = q ] && break
    file_pk="${HOME}/.ssh/${uid}".pub

    gpg -K --with-subkey-fingerprint "${uid}"
    echo 'What is the fingerprint of the authentication *subkey* to export as SSH key: '
    read -r fingerprint
    keygrip="$(gpg -K --with-subkey-fingerprint --with-keygrip "${uid}" | awk "/${fingerprint}/ {getline; print \$3}")"
    if [ "${#keygrip}" -ne 40 ]; then # keygrips are always 40 characters long
        gpg -K --with-subkey-fingerprint --with-keygrip "${uid}"
        echo "Could not automatically find the keygrip of subkey with ${fingerprint}, please enter it manually: "
        read -r keygrip
    fi
    if [ -f "${file_pk}" ]; then
        echo "File ${file_pk} already exists."
    else
        confirm_info || continue
        gpg --output "${file_pk}" --export-ssh-key "${fingerprint}"!
        chmod 0600 "${file_pk}"
        echo "${keygrip}" 0 >> "${GNUPGHOME:-~/.gnupg}"/sshcontrol
    fi
done
exit

5 Setup ssh_config and git-config for Multiple SSH Keys

5.1 Simple Configuration

Now you can use the public keys you just created in your ~/.ssh/config to let ssh know when to use which key. A simple configuration could look like:

Host gitlab.com
  IdentityFile ~/.ssh/gitlab.pub
  PreferredAuthentications publickey

Host github.com
  IdentityFile ~/.ssh/github.pub
  PreferredAuthentications publickey

This will make ssh use the gitlab.pub key when the host is gitlab.com and github.pub when the host is github.com.

5.2 Multiple Github Accounts

When you have multiple account at the same host and you can use the same key e.g. github.com then the above configuration will not work. You will need to modify your ~/.config/git/config and ~/.ssh/config to make this setup work. It also requires a specific directory structure.

This directory structure is necessary so git can be configured to use the correct ssh command and other options if required.

projects-directory
├── github-account-1
│   ├── project-1
│   ├── project-2
├── github-account-2
│   ├── project-3
│   ├── project-4

5.2.1 Setup ssh_config

~/.ssh/config #+begin_src conf-unix -n

Host github.com-account1 User git HostName github.com IdentityFile ~/.ssh/github1.pub PreferredAuthentications publickey

Host github.com-account2 User git HostName github.com IdentityFile ~/.ssh/github2.pub PreferredAuthentications publickey #+end_src unix

For the second account, the github.com-account2 is just an alias and the User and HostName are what ssh actually uses as user and host i.e. ssh User@HostName. We can now take advantage of this alias in the ~/.config/git/config.

5.2.2 Setup git-config

~/.config/git/config #+begin_src conf-unix -n [user] name = Your Name

useConfigOnly = true [init] defaultBranch = main

[includeIf "gitdir:~/projects-directory/github1/"] path = ~/.config/git/config.github1 [includeIf "gitdir:~/projects-directory/github2/"] path = ~/.config/git/config.github2 #+end_src unix

The includeIf allows git to load settings based on the location of the project. So earch github account will get its own git config file with the location specified in the path.

github1 is the default account so it is basically the same as in the simple configuration except here commit signing is turned on, since you have a gpg key you might as well sign your commits. The gpg email can be found with gpg -K.

Note: to sign git commits and have them verified by github your gpg public key must added to your github account and the gpg email must be the same as the email used in github. You can use the no-reply email if you want to keep your email private.

~/.config/git/config.github1 #+begin_src conf-unix -n [user] email = github1@gmail.com signingKey = <gpg-key-id> [url "git@github.com-account1"] insteadOf = git@github.com [commit] gpgsign = true [tag] gpgsign = true #+end_src unix

~/.config/git/config.github2 #+begin_src conf-unix -n [user] email = github2@gmail.com signingKey = <gpg-key-id> [url "git@github.com-account2"] insteadOf = git@github.com [commit] gpgsign = true [tag] gpgsign = true #+end_src unix

Note that the github.com-account1 and github.com-account2 part of the urls are the same as the host aliases defined in ~/.ssh/config earlier. This will make sure that ssh will use the correct ssh key. This only have when inside ~/projects-directory/github1 or ~/projects-directory/github2.

6 Add SSH and GPG Keys to Github/lab

This step depends on which platform you use, so read the respective docs or search the answer on duckduckgo.

7 Errors

If you get an error like agent refused operation either try changing pinentry progams in ~/.gnupg/gpg-agent.conf or add this to your shell config file, e.g. ~/.bashrc:

gpg-connect-agent UPDATESTARTUPTTY /bye

This will ensure that the gpg-agent is started for ssh support.

8 Sources

Footnotes:



If something is not working, please create an issue here.