Go version
go version go1.24.4 (Red Hat 1.24.4-1.module+el8.10.0+23323+67916f33) linux/amd64
Output of go env
in your module/workspace:
AR='ar'
CC='gcc'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='g++'
GCCGO='gccgo'
GO111MODULE=''
GOAMD64='v1'
GOARCH='amd64'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/opt/app-root/src/.cache/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/opt/app-root/src/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build2709008956=/tmp/go-build -gno-record-gcc-switches'
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMOD='/dev/null'
GOMODCACHE='/opt/app-root/src/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/opt/app-root/src/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/lib/golang'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/opt/app-root/src/.config/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='local'
GOTOOLDIR='/usr/lib/golang/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.24.4 (Red Hat 1.24.4-1.module+el8.10.0+23323+67916f33)'
GOWORK=''
PKG_CONFIG='pkg-config'
What did you do?
Compiled the below source code for FIPS compliance as
RUN CGO_ENABLED=1 GOOS=linux GOEXPERIMENT=strictfipsruntime go build -tags strictfipsruntime -buildvcs=false -o github-ssh-client .
Full source code and Dockerfile available at https://github.com/anandf/ssh-fips
package main
import (
"crypto/fips140"
"log"
"os"
"path/filepath"
)
import (
"golang.org/x/crypto/ssh"
)
func main() {
log.Printf("Fips enabled? %v\n", fips140.Enabled())
homeDir, err := os.UserHomeDir()
if err != nil {
log.Fatalf("error when getting user's home directory: %v", err)
}
sshKeyPath := filepath.Join(homeDir, ".ssh", "id_rsa")
sshKey, err := os.ReadFile(sshKeyPath)
if err != nil {
log.Fatalf("error when reading private key from %s: %v", sshKeyPath, err)
}
sshKeySigner, err := ssh.ParsePrivateKey(sshKey)
if err != nil {
log.Fatalf("error when parsing private key: %v", err)
}
cfg := &ssh.ClientConfig{
User: "git",
Auth: []ssh.AuthMethod{ssh.PublicKeys(sshKeySigner)},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
cfg.SetDefaults()
//cfg.Ciphers = []string{"aes256-gcm@openssh.com"}
cfg.KeyExchanges = []string{"curve25519-sha256"}
conn, err := ssh.Dial("tcp", "github.com:22", cfg)
if err != nil {
log.Fatalf("Failed to dial: %v", err)
}
defer conn.Close()
log.Println("SSH connection was successful")
}
What did you see happen?
When running in a cluster in FIPS enabled mode, the program crashes with the below panic and stack trace.
2025/08/18 07:15:22 Fips enabled? false
panic: curve25519: internal error: scalarBaseMult was not 32 bytes
goroutine 16 [running]:
golang.org/x/crypto/curve25519.ScalarBaseMult(0xc0001343a0, 0x6c2a50?)
/opt/app-root/src/go/pkg/mod/golang.org/x/crypto@v0.41.0/curve25519/curve25519.go:39 +0x13b
golang.org/x/crypto/ssh.(*curve25519KeyPair).generate(0xc000134380, {0x6c4dc0?, 0x6c2a50?})
/opt/app-root/src/go/pkg/mod/golang.org/x/crypto@v0.41.0/ssh/kex.go:457 +0x65
golang.org/x/crypto/ssh.(*curve25519sha256).Client(0xa30740?, {0x7f739b2feb18, 0xc000116120}, {0x6c4dc0, 0x6c2a50}, 0xc00006a540)
/opt/app-root/src/go/pkg/mod/golang.org/x/crypto@v0.41.0/ssh/kex.go:468 +0x6e
golang.org/x/crypto/ssh.(*handshakeTransport).client(0xc0001084e0, {0x6c5208?, 0xa79720?}, 0xc00006a540?)
/opt/app-root/src/go/pkg/mod/golang.org/x/crypto@v0.41.0/ssh/handshake.go:826 +0x66
golang.org/x/crypto/ssh.(*handshakeTransport).enterKeyExchange(0xc0001084e0, {0xc0000dc300, 0x2d8, 0x2d8})
/opt/app-root/src/go/pkg/mod/golang.org/x/crypto@v0.41.0/ssh/handshake.go:716 +0x49a
golang.org/x/crypto/ssh.(*handshakeTransport).kexLoop(0xc0001084e0)
/opt/app-root/src/go/pkg/mod/golang.org/x/crypto@v0.41.0/ssh/handshake.go:345 +0x98
created by golang.org/x/crypto/ssh.newClientTransport in goroutine 1
/opt/app-root/src/go/pkg/mod/golang.org/x/crypto@v0.41.0/ssh/handshake.go:170 +0x256
What did you expect to see?
The key exchange algorithm curve25519
is not FIPS compliant. There should be graceful error handling that says that the selected algorithim is not FIPS compliant and if there are other available FIPS compliant algorithims available in the list, those should be prioritized for the key exchange.
Comment From: gabyhelp
Related Issues
- x/crypto/ssh: curve25519-sha256@libssh.org is now curve25519-sha256 #48756 (closed)
- x/crypto/ssh agent handshake fails with normally working configuration #29086 (closed)
- x/crypto: No working with go1.13 #34734 (closed)
- crypto/internal/fips140/subtle: Go 1.25 subtle.xorBytes panic on MIPS #74998
- x/crypto/ssh: Corrupting a byte of a clients private key results in panic, rather than authorization failure #42014 (closed)
- crypto/internal/fips140: segfault from hmac memmove #70880 (closed)
- x/crypto/ssh: invalid packet length, packet too large #54719 (closed)
- x/crypto: curve25519.ladderstep runs into segmentation fault when invoked under emulation of qemu-x86_64 on an ARMv6 host #44572
- x/crypto/ssh: unable to parse certificates #45010 (closed)
- x/crypto/ssh: TestValidTerminalMode fails with some shells #38037 (closed)
(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)
Comment From: cagedmantis
cc @drakkan @golang/security @FiloSottile
Comment From: FiloSottile
strictfipsruntime
is not an upstream option, so you are using a downstream unsupported fork.
This would probably work out the same way with the supported GODEBUG=fips140=only
, though.
fips140=only
is known to be overly strict, and there is a discussion at #74630 on how to improve it.
In parallel, it might make sense for x/crypto/ssh to gain a FIPS mode like crypto/tls, which disables non-FIPS algorithms.
Comment From: anandf
Thanks @FiloSottile.
I tried using the supported golang (docker.io/library/golang:1.24.4) and am able to reproduce this problem with runtime setting GODEBUG=fips140=only
.
We are working around the issue by setting GODEBUG
env to fips140=on
. I was wondering if there was a graceful handling of this error, instead of panic, thereby the SSH handshake can happen with a next FIPS compliant algorithm in the list of key exchange algorithm. This is the fix we have done to remove the problematic algorithm if FIPS is enabled. https://github.com/argoproj/argo-cd/pull/24086
Comment From: gopherbot
Change https://go.dev/cl/696996 mentions this issue: curve25519: include potential fips140=only error in panic message
Comment From: gopherbot
Change https://go.dev/cl/696995 mentions this issue: ssh: use curve25519.X25519 instead of curve25519.ScalarMult
Comment From: FiloSottile
No, because of how the SSH protocol works, there is no way to change the algorithms once they have been selected, so the best we can do is fail the handshake with an error instead of a panic. That's what CL 696995 does.
To avoid breaking the connection, you need to remove non-FIPS algorithms from the config. For now, you'll have to do it manually, but we will consider doing it automatically like in crypto/tls.
Comment From: anandf
Thanks @FiloSottile for your valuable inputs. I have removed the non FIPS compliant algorithm in our code when host is running in FIPS mode as part of https://github.com/argoproj/argo-cd/pull/24086.
Comment From: gopherbot
Change https://go.dev/cl/698795 mentions this issue: ssh: add support for FIPS-only mode