Skip to content

Instantly share code, notes, and snippets.

@BertanT
Last active March 31, 2025 01:09
Show Gist options
  • Save BertanT/9d222da115ca2d1274ef34735c4260cf to your computer and use it in GitHub Desktop.
Save BertanT/9d222da115ca2d1274ef34735c4260cf to your computer and use it in GitHub Desktop.
macOS OpenSSH Client Patcher for Hardware Security Key Support (ED25519-SK With YubiKey Etc.)

🔐 macOS OpenSSH Patcher for Hardware Security Keys

Supports ED25519-SK with Yubikey and other FIDO2 hardware security keys!

🤔 Discussion

Despite being compiled to support hardware security keys that take advantage of the FIDO2 protocol, the built-in OpenSSH client on macOS Sonoma and above lacks the middleware/library to support these devices. To keep using the built-in client - which is often the most stable and secure method for SSH connections - we need to compile the Security Key Provider from OpenSSH source and tell the macOS client about it ourselves.

This script does all of that for you on both Apple Silicon and Intel Mac computers!

The script installs openssl and libfido2 along with the required build tools from Homebrew. It then clones the latest main branch of OpenSSH Portable and builds from it the Security Key Provider library: sk-libfido2.dylib. It finally moves the built library to /usr/local/lib/, modifies ~/.zshenv to export the environment variable SSH_SK_PROVIDER=/usr/local/lib/sk-libfido2.dylib to tell the SSH client about it, and cleans everything up!

📝 Instructions

Before getting started, make sure you:

  • are running macOS 14.0 Sonoma or higher. See the next section for solutions on older versions.
  • have Xcode Command Line Tools installed. You can check/install it by typing xcode-select --install in your Terminal.
  • have Homebrew installed. Click here for instructions.

Then, proceed with the installation:

  • Save macskeyinstaller.sh below to your Downloads folder. You can do this by right-clicking the Raw button and selecting Download Linked File.
  • Open a new Terminal and go to your Downloads folder by executing cd Downloads.
  • Run the script by executing bash macskeyinstaller.sh. Do not run with sudo.
  • Enter your login password when prompted as copying the library to /usr/local/lib/ requires root privileges.
  • When the script is done running, restart your Terminal for the changes to take effect.

Use your hardware security key to create a new ED25519-SK SSH Key:

There are great instructions on this on the Yubico website! Follow the instructions for Linux instead of macOS! Click here to see them.

📕Helpful Reading

These discussions and forum posts were what enabled and inspired me to create this script. I highly suggest checking them out for more technical details.

Copyright 2025 Mehmet Bertan Tarakcioglu (github.com/BertanT), under the MIT License.
#!/bin/bash
################################################################################################
# macskeyinstaller.sh
# Last Updated: January 13, 2025
#
# macOS OpenSSH Client Patcher for Hardware Security Key Support (ED25519-SK With YubiKey Etc.)
# Check out https://gist.github.com/BertanT/9d222da115ca2d1274ef34735c4260cf for details!
#
# Copyright 2025 Mehmet Bertan Tarakcioglu (github.com/BertanT), under the MIT License.
################################################################################################
# Exit early if there is an error
set -e
printf "\n* Hello! This script compiles and installs the security key provider for the built-in macOS Open SSH client to enable support for hardware security keys (such as a YubiKey)."
# Check if the script is running as root
if [ "$(id -u)" -eq 0 ]; then
printf "\n!!! Error: For safety reasons, this script cannot be run as root. If using sudo, please try again without it.\n" >&2
exit 1
fi
# Function to compare macOS version strings
version_ge() {
[[ "$(echo -e "$1\n$2" | sort -V | head -n1)" == "$2" ]]
}
# function to get the current macOS Version
macos_version=$(sw_vers -productVersion | cut -d '.' -f1,2)
# Check if running at least macOS Sonoma
if ! version_ge "$macos_version" "14.0"; then
printf "\n!!! Error: macOS version is $macos_version. This script requires at least macOS Sonoma (14.0).\n" >&2
exit 1
fi
printf "\n* macOS version is supported!"
# Check if Homebrew is installed
if ! which brew &>/dev/null; then
printf "\n!!! Error: This script requires Homebrew to install dependencies! Please install Homebrew and try again\n" >&2
exit 1
fi
printf "\n* Homebrew is installed!"
printf "\n* Cloning the latest main branch of openssh-portable from GitHub. This may take a while..."
git clone --quiet https://github.com/openssh/openssh-portable.git
cd openssh-portable
printf "\n* Installing dependencies from Homebrew: libfido2, openssl, autoconf, automake, libtool\n"
brew install libfido2 openssl autoconf automake libtool pkgconf
printf "\n* Generating configuration script."
autoreconf -i
printf "\n* Exporting flags."
# Determine the CPU architecture and set the appropriate Homebrew base path
if [[ "$(uname -m)" == "arm64" ]]; then
BREW_PREFIX="/opt/homebrew"
elif [[ "$(uname -m)" == "x86_64" ]]; then
BREW_PREFIX="/usr/local"
else
echo "!!! Error: Unknown CPU architecture $(uname -m).\n" >&2
exit 1
fi
# Get the appropriate paths for the Homebrew dependecies as they differ through version
BREW_OPENSSL_PATH=$(ls -d $BREW_PREFIX/Cellar/openssl@3/*)
BREW_LIBFIDO2_PATH=$(ls -d $BREW_PREFIX/Cellar/libfido2/*)
export CFLAGS="-L$BREW_OPENSSL_PATH/lib -I$BREW_OPENSSL_PATH/include -L$BREW_LIBFIDO2_PATH/lib -I$BREW_LIBFIDO2_PATH/include -Wno-error=implicit-function-declaration"
export LDFLAGS="-L$BREW_OPENSSL_PATH/lib -L$BREW_LIBFIDO2_PATH/lib"
printf "\n* Configuring build. This may take a while..."
./configure --quiet --with-security-key-standalone
printf "\n* Cleaning previous build for good measure."
make --quiet clean
printf "\n* Building OpenSSH Portable."
make --quiet
printf "\n* Copying the Security Key Provider library to /usr/local/lib."
printf "\n We need root privileges to modify system files.\n"
sudo mkdir -p /usr/local/lib
sudo mv sk-libfido2.dylib /usr/local/lib/
# Check if the script is running in ZSH. If not, give the user instructions.
# Otherwise, modify .zshenv to set the environment variable if not already present.
if [[ "$SHELL" == *zsh* ]]; then
printf "\n* Configuring the ~/.zshenv for the System SSH use the Security Key Provider we just built"
if ! grep -q "export SSH_SK_PROVIDER=/usr/local/lib/sk-libfido2.dylib" "$HOME/.zshenv" &>/dev/null; then
echo "export SSH_SK_PROVIDER=/usr/local/lib/sk-libfido2.dylib" >> "$HOME/.zshenv"
printf "\n* Added SSH_SK_PROVIDER to ~/.zshenv."
else
printf "\n* SSH_SK_PROVIDER is already configured in ~/.zshenv."
fi
else
printf "*\n Since you are not on ZSH, you will need to manually configure your shell profile to tell the System SSH client to use the Security Key Provider we just built."
printf "\n The shell profile should export the environment variable as follows:"
printf "\n export SSH_SK_PROVIDER=/usr/local/lib/sk-libfido2.dylib"
fi
printf "\n* Exiting the directory and deleting the repository we cloned. We don't need it anymore"
cd ..
rm -rf openssh-portable
printf "\n\n* That's it! After restarting your terminal session, you can plug in your hardware security key and test the installation using:"
printf "\n ssh-keygen -t ed25519-sk -O resident -O verify-required -C \"Your Comment\""
printf "\n\nHave a nice day! :)\n\n"
@BertanT
Copy link
Author

BertanT commented Feb 2, 2025

Hello @githubaff0!

Thank you so much for looking into this. Your fix seems to solve the problem!

I first checked my computer and I indeed had pkgconf already installed via brew - I must have missed it when writing up the script. I have now updated the script to install it. After also testing the new version on a friend's computer, it works as expected.

If you run into any issues or need any other help regarding the script, please let me know. Thank you so much again for resolving the issue :)

@erilor
Copy link

erilor commented Feb 18, 2025

Thank you for this! Got it working thanks to your script.

One suggestion is to add set -e (or something else to get the script to stop if something fails). I had two versions of openssl in brew which made the config fail, but since the script kept going I thought everything worked (and the cloned repo, including the config.log, was deleted).

@BertanT
Copy link
Author

BertanT commented Mar 27, 2025

@erilor Sorry I got completely sidetracked about this. I updated to script as you suggested. Thank you so much for your help :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment