From cee12b2bd16b001153051fa8cbaacb7b1fe1403b Mon Sep 17 00:00:00 2001 From: Igor Soarez Date: Mon, 2 Nov 2026 13:09:27 +0200 Subject: yubikey-git: three keys, one per YubiKey; any one of them will do --- bin/yubikey-git | 46 ++++++++++++++-------- config/ssh/yubikey-git/config | 16 +++++--- .../ssh/yubikey-git/id_ed25519_sk_git_backup_nfc | 9 +++++ .../yubikey-git/id_ed25519_sk_git_backup_nfc.pub | 1 + config/ssh/yubikey-git/id_ed25519_sk_git_main_nfc | 9 +++++ .../ssh/yubikey-git/id_ed25519_sk_git_main_nfc.pub | 1 + 6 files changed, 59 insertions(+), 23 deletions(-) create mode 100644 config/ssh/yubikey-git/id_ed25519_sk_git_backup_nfc create mode 100644 config/ssh/yubikey-git/id_ed25519_sk_git_backup_nfc.pub create mode 100644 config/ssh/yubikey-git/id_ed25519_sk_git_main_nfc create mode 100644 config/ssh/yubikey-git/id_ed25519_sk_git_main_nfc.pub diff --git a/bin/yubikey-git b/bin/yubikey-git index 9083097..6e46c1f 100755 --- a/bin/yubikey-git +++ b/bin/yubikey-git @@ -1,12 +1,12 @@ #!/bin/bash -# Opt this host in to git over ssh with the YubiKey git key, or back out. +# Opt this host in to git over ssh with the YubiKey git keys, or back out. # # yubikey-git enable set it up; safe to re-run # yubikey-git disable undo everything enable did # yubikey-git status what is in place # -# Then, with the YubiKey plugged in, one touch per fetch or push: +# Then, with any one of the YubiKeys plugged in, one touch per fetch or push: # # git clone git@git.soarez.net:sz/.git # @@ -16,11 +16,11 @@ # # What enable does, all of it undone by disable: # -# ~/.ssh/id_ed25519_sk_git a copy of config/ssh/yubikey-git's key, at +# ~/.ssh/id_ed25519_sk_git* copies of config/ssh/yubikey-git's keys, at # mode 600, which ssh insists on and git -# cannot store. It is the key's handle, not +# cannot store. Each is a key's handle, not # its private half: see that directory's -# config for why it is safe in this repo. +# config for why they are safe in this repo. # ~/.config/ssh/yubikey-git a link to that directory: the ssh config # and git.soarez.net's pinned host key # ~/.config/ssh/enabled.d/yubikey-git.conf @@ -36,7 +36,8 @@ set -euo pipefail name="yubikey-git" -key="${HOME}/.ssh/id_ed25519_sk_git" +# One per YubiKey, each its own key; any of them will do. +keys=(id_ed25519_sk_git_main_nfc id_ed25519_sk_git_backup_nfc id_ed25519_sk_git) # The same path config/ssh/entry includes from, not $XDG_CONFIG_HOME. cfg="${HOME}/.config/ssh" dir_link="${cfg}/${name}" @@ -82,14 +83,18 @@ need_fido_ssh() { } enable() { + local k key need_fido_ssh install -d -m 700 "${HOME}/.ssh" - if [[ -e "${key}" ]] && ! cmp -s "${key}" "${src_dir}/id_ed25519_sk_git"; then - die "${key} exists and is a different key; move it aside first" - fi - install -m 600 "${src_dir}/id_ed25519_sk_git" "${key}" - install -m 644 "${src_dir}/id_ed25519_sk_git.pub" "${key}.pub" + for k in "${keys[@]}"; do + key="${HOME}/.ssh/${k}" + if [[ -e "${key}" ]] && ! cmp -s "${key}" "${src_dir}/${k}"; then + die "${key} exists and is a different key; move it aside first" + fi + install -m 600 "${src_dir}/${k}" "${key}" + install -m 644 "${src_dir}/${k}.pub" "${key}.pub" + done mkdir -p "${cfg}/enabled.d" if [[ -e "${dir_link}" || -L "${dir_link}" ]] && ! ours "${dir_link}" "${src_dir}"; then @@ -108,24 +113,31 @@ enable() { die "ssh did not pick the config up, so it is undone; is ~/.ssh/config this repo's config/ssh/entry?" fi - echo "${name}: enabled. With the YubiKey plugged in:" + echo "${name}: enabled. With one of the YubiKeys plugged in:" echo " git clone git@git.soarez.net:sz/.git" } disable() { + local k key ours "${conf_link}" "${dir_link}/config" && rm "${conf_link}" ours "${dir_link}" "${src_dir}" && rm "${dir_link}" - if [[ -f "${key}" ]] && cmp -s "${key}" "${src_dir}/id_ed25519_sk_git"; then - rm -f "${key}" "${key}.pub" - fi + for k in "${keys[@]}"; do + key="${HOME}/.ssh/${k}" + if [[ -f "${key}" ]] && cmp -s "${key}" "${src_dir}/${k}"; then + rm -f "${key}" "${key}.pub" + fi + done echo "${name}: disabled" } status() { - local on=y + local on=y k key if ours "${conf_link}" "${dir_link}/config"; then echo "ssh config: enabled (${conf_link})"; else echo "ssh config: not enabled"; on=""; fi if ours "${dir_link}" "${src_dir}"; then echo "config dir: ${dir_link}"; else echo "config dir: missing"; on=""; fi - if [[ -f "${key}" ]] && cmp -s "${key}" "${src_dir}/id_ed25519_sk_git"; then echo "key handle: ${key}"; else echo "key handle: missing"; on=""; fi + for k in "${keys[@]}"; do + key="${HOME}/.ssh/${k}" + if [[ -f "${key}" ]] && cmp -s "${key}" "${src_dir}/${k}"; then echo "key handle: ${key}"; else echo "key handle: ${k} missing"; on=""; fi + done echo "ssh: $(command -v ssh) ($(ssh -V 2>&1))" [[ -n "${on}" ]] } diff --git a/config/ssh/yubikey-git/config b/config/ssh/yubikey-git/config index a878c92..f74db8f 100644 --- a/config/ssh/yubikey-git/config +++ b/config/ssh/yubikey-git/config @@ -1,16 +1,20 @@ -# git over ssh to git.soarez.net with the YubiKey git key: git clone -# git@git.soarez.net:sz/.git, one touch of the key per fetch or push. +# git over ssh to git.soarez.net with the YubiKey git keys: git clone +# git@git.soarez.net:sz/.git, one touch of a key per fetch or push. # # Read only on a host that opted in: `yubikey-git enable` links this file into # ~/.config/ssh/enabled.d/, which config/ssh/entry includes. See bin/yubikey-git. # -# The key is id_ed25519_sk_git beside this file, a FIDO2 key whose private half -# never leaves the YubiKey. The file is only its handle: useless without the -# YubiKey, and the YubiKey signs nothing without a touch. git.soarez.net accepts -# it for git on the repositories and nothing else. +# The keys are the id_ed25519_sk_git* files beside this file, one FIDO2 key per +# YubiKey, whose private half never leaves it. Each file is only a handle: +# useless without its YubiKey, and the YubiKey signs nothing without a touch. +# git.soarez.net accepts them for git on the repositories and nothing else. Any +# one will do: ssh passes over those whose YubiKey is not plugged in. After +# adding one here, re-run `yubikey-git enable` on each host that opted in. Host git.soarez.net User git + IdentityFile ~/.ssh/id_ed25519_sk_git_main_nfc + IdentityFile ~/.ssh/id_ed25519_sk_git_backup_nfc IdentityFile ~/.ssh/id_ed25519_sk_git IdentitiesOnly yes # Pinned, not learned: on a machine we do not trust, the first connection is diff --git a/config/ssh/yubikey-git/id_ed25519_sk_git_backup_nfc b/config/ssh/yubikey-git/id_ed25519_sk_git_backup_nfc new file mode 100644 index 0000000..eacefe9 --- /dev/null +++ b/config/ssh/yubikey-git/id_ed25519_sk_git_backup_nfc @@ -0,0 +1,9 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAABpzay1zc2 +gtZWQyNTUxOUBvcGVuc3NoLmNvbQAAACBE1NcemsA6aigy5wfYux6V7NITOnpUC8+hOjaT +hFFgzQAAAARzc2g6AAAA8NK/OQXSvzkFAAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY2 +9tAAAAIETU1x6awDpqKDLnB9i7HpXs0hM6elQLz6E6NpOEUWDNAAAABHNzaDoBAAAAgIBx +G++mRhZdhD+F5F/LCFg+miSuX+bCL1Vim5K9tG9mcBXZyqetZkzxfyMtOiCJJQ6ZLDYijA +TMFSiqJd0eb/bbq+xad55ELGZ4DwqUvwUUybcqUcya43XIa5tGBLg2hOwVsMr8oR6FdU2E +msg9QdFvEA3DrQYeZpD9RGSV303JAAAAAAAAAA5iYWNrdXAtbmZjLWdpdAECAw== +-----END OPENSSH PRIVATE KEY----- diff --git a/config/ssh/yubikey-git/id_ed25519_sk_git_backup_nfc.pub b/config/ssh/yubikey-git/id_ed25519_sk_git_backup_nfc.pub new file mode 100644 index 0000000..c659e53 --- /dev/null +++ b/config/ssh/yubikey-git/id_ed25519_sk_git_backup_nfc.pub @@ -0,0 +1 @@ +sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIETU1x6awDpqKDLnB9i7HpXs0hM6elQLz6E6NpOEUWDNAAAABHNzaDo= backup-nfc-git diff --git a/config/ssh/yubikey-git/id_ed25519_sk_git_main_nfc b/config/ssh/yubikey-git/id_ed25519_sk_git_main_nfc new file mode 100644 index 0000000..6b5b7cc --- /dev/null +++ b/config/ssh/yubikey-git/id_ed25519_sk_git_main_nfc @@ -0,0 +1,9 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAABpzay1zc2 +gtZWQyNTUxOUBvcGVuc3NoLmNvbQAAACAP4g7w6EiRrYeb9C04vpmcO6ECKghdFvR16wEF +CKFbUgAAAARzc2g6AAAA8CjpEMYo6RDGAAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY2 +9tAAAAIA/iDvDoSJGth5v0LTi+mZw7oQIqCF0W9HXrAQUIoVtSAAAABHNzaDoBAAAAgFUe +jvEOuayDnp4QwQGUyp03HM/y8LFFQwq3Uqou36lhMCWrg974AIYQzQsTO/oqHkNKA2c7+d +simGavu35U5MLqNx7orFJOoU879fAhW0vo/eu80cnXKHpDqImRQsiYt6OohwT1PvkzLH5N +GlJ5CMHlyAiO1pnHVWfawcDRxJ1YAAAAAAAAAAxtYWluLW5mYy1naXQBAgMEBQ== +-----END OPENSSH PRIVATE KEY----- diff --git a/config/ssh/yubikey-git/id_ed25519_sk_git_main_nfc.pub b/config/ssh/yubikey-git/id_ed25519_sk_git_main_nfc.pub new file mode 100644 index 0000000..b6c6cf5 --- /dev/null +++ b/config/ssh/yubikey-git/id_ed25519_sk_git_main_nfc.pub @@ -0,0 +1 @@ +sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIA/iDvDoSJGth5v0LTi+mZw7oQIqCF0W9HXrAQUIoVtSAAAABHNzaDo= main-nfc-git -- cgit v1.2.3