CVE-2026-39831: golang.org/x/crypto/ssh FIDO/U2F User Presence Check Bypass
golang.org/x/crypto/ssh skECDSA/skEd25519 Verify() never checked the FIDO UP flag, letting touch-free signatures authenticate on any server before v0.52.0.

The problem
The Verify() method for sk-ecdsa-sha2-nistp256@openssh.com and sk-ssh-ed25519@openssh.com security-key types in golang.org/x/crypto/ssh < 0.52.0 parsed but never validated the FIDO/U2F User Presence (UP) flag byte in the authenticator data. Any software FIDO authenticator or modified client could therefore produce a valid-looking SK signature with the UP bit cleared (flags=0x00), authenticating to an SSH server without the user ever physically touching the hardware key.
This allows fully unattended, automated abuse of accounts that are expected to require physical presence, defeating the primary security guarantee of hardware security keys.
Proof of concept
# Minimal sk-ed25519 authenticator data blob with UP flag = 0x00 (no touch)
# Wire format for sk-ssh-ed25519@openssh.com signature (relevant fields):
#
# [32-byte application hash] [flags byte = 0x00] [4-byte counter] [ed25519 sig]
#
# Before the patch, Verify() accepted this with flags=0x00.
# After the patch, Verify() rejects it unless Permissions.Extensions["no-touch-required"] is set.
import struct, hashlib
app_hash = hashlib.sha256(b"ssh:").digest() # 32 bytes
flags = b"\x00" # UP=0, UV=0 — no physical touch
counter = struct.pack(">I", 1) # 4-byte big-endian counter
# Attacker supplies a real ed25519 sig over (app_hash + flags + counter + client_data_hash)
# using a software-only FIDO token (e.g. python-fido2 soft authenticator).
# The crafted authenticator_data is embedded in the SSH_MSG_USERAUTH_REQUEST.
authenticator_data = app_hash + flags + counter
print(authenticator_data.hex()) # embed in SK signature blob; old Verify() accepts itThe root cause is a missing authorization check (CWE-862): skECDSAPublicKey.Verify() and skEd25519PublicKey.Verify() both decoded the authenticator-data flags byte but never asserted that the UP bit (0x01) was set, violating the FIDO/U2F spec which mandates rejection when UP=0.
The patch at CL 781662 adds an explicit flags-byte check in both Verify() methods and gates bypass on the server explicitly returning the 'no-touch-required' extension via Permissions.Extensions in PublicKeyCallback, making the default safe. A soft FIDO2 authenticator (e.g. python-fido2 in software mode) can trivially produce signatures with flags=0x00, which the unpatched Verify() accepted as fully valid.
The fix
Upgrade golang.org/x/crypto to v0.52.0 or later (`go get golang.org/x/crypto@v0.52.0`). If your server legitimately needs to allow touch-free SK signatures (e.g. for automation), explicitly opt in by returning `Permissions.Extensions["no-touch-required"] = ""` from your PublicKeyCallback, do NOT do this without a documented operational reason.
Reported by NCC Group Cryptography Services (sponsored by Teleport).