|
#!/bin/bash |
|
# build-sk-libfido2.sh |
|
# |
|
# Builds the OpenSSH FIDO2 security key provider (sk-libfido2.dylib) from |
|
# openssh-portable source for use with macOS's built-in SSH client. |
|
# |
|
# macOS ships OpenSSH with FIDO2 support compiled in, but without the |
|
# middleware library that talks to hardware keys. This script builds it, |
|
# statically linking libfido2/libcbor/libcrypto so there are no Homebrew |
|
# runtime dependencies. Build tools are removed afterward. |
|
# |
|
# Re-run at any time to rebuild from the latest source. |
|
# |
|
# SPDX-License-Identifier: 0BSD |
|
|
|
set -euo pipefail |
|
|
|
DEST="$HOME/.ssh/sk-libfido2.dylib" |
|
|
|
# Track which Homebrew packages are newly installed so we can remove only those. |
|
echo "==> Recording existing Homebrew packages" |
|
brew_before=$(brew list --formula -1 | sort) |
|
|
|
echo "==> Installing build dependencies" |
|
brew install libfido2 openssl@3 autoconf automake libtool pkgconf cmake |
|
|
|
brew_after=$(brew list --formula -1 | sort) |
|
new_packages=$(comm -13 <(echo "$brew_before") <(echo "$brew_after")) |
|
|
|
BREW_PREFIX=$(brew --prefix) |
|
OPENSSL=$(brew --prefix openssl@3) |
|
LIBFIDO2=$(brew --prefix libfido2) |
|
|
|
WORKDIR=$(mktemp -d) |
|
trap 'rm -rf "$WORKDIR"' EXIT |
|
|
|
# Homebrew's libcbor only has shared libraries. Build a static archive. |
|
echo "==> Building static libcbor" |
|
git clone --depth 1 --quiet https://github.com/pjk/libcbor.git "$WORKDIR/libcbor" |
|
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF \ |
|
-S "$WORKDIR/libcbor" -B "$WORKDIR/libcbor/_build" > /dev/null 2>&1 |
|
cmake --build "$WORKDIR/libcbor/_build" > /dev/null 2>&1 |
|
LIBCBOR_A="$WORKDIR/libcbor/_build/src/libcbor.a" |
|
|
|
# --with-security-key-standalone builds only the FIDO2 middleware, not all of openssh. |
|
echo "==> Cloning and building openssh sk-libfido2.dylib" |
|
git clone --depth 1 --quiet https://github.com/openssh/openssh-portable.git "$WORKDIR/openssh" |
|
cd "$WORKDIR/openssh" |
|
autoreconf -i > /dev/null 2>&1 |
|
if ! ./configure --with-security-key-standalone \ |
|
CFLAGS="-I$OPENSSL/include -I$LIBFIDO2/include" \ |
|
LDFLAGS="-L$OPENSSL/lib -L$LIBFIDO2/lib" \ |
|
> "$WORKDIR/configure.log" 2>&1; then |
|
tail -20 "$WORKDIR/configure.log" |
|
exit 1 |
|
fi |
|
if ! make V=1 sk-libfido2.dylib > "$WORKDIR/build.log" 2>&1; then |
|
tail -20 "$WORKDIR/build.log" |
|
exit 1 |
|
fi |
|
|
|
# --- Re-link with static archives --- |
|
# |
|
# make linked against Homebrew's shared libraries. We replay the link command |
|
# with three modifications: |
|
# - Replace -lfido2 with static libfido2.a + libcbor.a (libcbor is a |
|
# transitive dep that static linking won't resolve on its own) |
|
# - Replace -lcrypto with static libcrypto.a |
|
# - Add CoreFoundation and IOKit frameworks (needed by libfido2's HID |
|
# transport; the shared libfido2.dylib links them implicitly) |
|
|
|
echo "==> Re-linking with static libraries" |
|
|
|
# Join backslash-continuation lines, then extract the link command |
|
link_cmd=$(perl -pe 's/\\\n\s*/ /g' "$WORKDIR/build.log" \ |
|
| grep -F -- '-o sk-libfido2' | tail -1) |
|
|
|
if [ -z "$link_cmd" ]; then |
|
echo "ERROR: Could not extract link command from build log." |
|
exit 1 |
|
fi |
|
|
|
rm -f sk-libfido2.dylib |
|
|
|
# Swap -l flags for static .a paths, drop Homebrew -L paths, add frameworks |
|
link_cmd=$(echo "$link_cmd" | sed \ |
|
-e "s|-lfido2|$LIBFIDO2/lib/libfido2.a $LIBCBOR_A|" \ |
|
-e "s|-lcrypto|$OPENSSL/lib/libcrypto.a|g" \ |
|
-e "s|-L$BREW_PREFIX[^ ]*||g") |
|
link_cmd="$link_cmd -framework CoreFoundation -framework IOKit" |
|
|
|
eval "$link_cmd" |
|
|
|
# Verify only macOS system libraries remain |
|
echo "==> Library dependencies:" |
|
otool -L sk-libfido2.dylib |
|
|
|
static_ok=true |
|
if otool -L sk-libfido2.dylib | grep -q "$BREW_PREFIX"; then |
|
echo "WARNING: Library still has Homebrew runtime dependencies." |
|
static_ok=false |
|
else |
|
echo "OK: No Homebrew runtime dependencies." |
|
fi |
|
|
|
# Install |
|
echo "==> Installing to $DEST" |
|
mkdir -p ~/.ssh && chmod 700 ~/.ssh |
|
cp sk-libfido2.dylib "$DEST" |
|
chmod 644 "$DEST" |
|
|
|
# Add SecurityKeyProvider to ~/.ssh/config (prepend for global scope) |
|
[ -f ~/.ssh/config ] || touch ~/.ssh/config |
|
chmod 600 ~/.ssh/config |
|
if ! grep -q "^SecurityKeyProvider" ~/.ssh/config; then |
|
echo "==> Adding SecurityKeyProvider to ~/.ssh/config" |
|
{ echo "SecurityKeyProvider $DEST"; echo ""; cat ~/.ssh/config; } > ~/.ssh/config.tmp |
|
mv ~/.ssh/config.tmp ~/.ssh/config |
|
chmod 600 ~/.ssh/config |
|
fi |
|
|
|
# Remove build deps that weren't previously installed |
|
if [ "$static_ok" = true ] && [ -n "${new_packages:-}" ]; then |
|
echo "==> Removing build-only dependencies" |
|
# shellcheck disable=SC2086 |
|
brew uninstall --ignore-dependencies $new_packages 2>/dev/null || true |
|
fi |
|
|
|
echo "==> Done. Installed $DEST" |