-
Notifications
You must be signed in to change notification settings - Fork 18.4k
Open
golang/crypto
#322Labels
BugReportIssues describing a possible bug in the Go implementation.Issues describing a possible bug in the Go implementation.NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Milestone
Description
Go version
go version go1.24.6 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='/home/heshamcli/.cache/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/home/heshamcli/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build4145496535=/tmp/go-build -gno-record-gcc-switches'
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMOD='/dev/null'
GOMODCACHE='/home/heshamcli/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/home/heshamcli/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/opt/go'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/home/heshamcli/.config/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/opt/go/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.24.6'
GOWORK=''
PKG_CONFIG='pkg-config'
What did you do?
I set up an SSH server using golang.org/x/crypto/ssh
and attempted to connect with a client (Paramiko) that tries multiple keys within a single SSH connection.
Paramiko sends a new SSH_MSG_SERVICE_REQUEST
before each key attempt.
The following is a minimal, reduced version of a real production use case that reproduces the issue.
Minimal reproducible Go server example:
package main
import (
"fmt"
"log"
"net"
"golang.org/x/crypto/ssh"
)
func main() {
private, err := ssh.ParsePrivateKey([]byte(testServerKey))
if err != nil {
log.Fatalf("failed to parse private key: %v", err)
}
config := &ssh.ServerConfig{NoClientAuth: false}
config.AddHostKey(private)
config.PublicKeyCallback = func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
// No keys are accepted — force failure to test multiple attempts.
fmt.Println("Key attempt from:", conn.User())
return nil, fmt.Errorf("unauthorized")
}
listener, err := net.Listen("tcp", ":2222")
if err != nil {
log.Fatalf("failed to listen on 2222: %v", err)
}
log.Println("Listening on :2222")
for {
conn, err := listener.Accept()
if err != nil {
log.Fatalf("failed to accept incoming connection: %v", err)
}
go func(c net.Conn) {
_, chans, reqs, err := ssh.NewServerConn(c, config)
if err != nil {
log.Printf("handshake failed: %v", err)
return
}
go ssh.DiscardRequests(reqs)
for ch := range chans {
ch.Reject(ssh.Prohibited, "no channels accepted")
}
}(conn)
}
}
const testServerKey = `-----BEGIN RSA PRIVATE KEY-----
MIIBOgIBAAJBANlfqF8A3gBzgU6bEqkG6nRzrtNdEPr+HMX3/fOjYbt5tI/gA2w7
s+pu5PxtjLMToCbMSqXnsRUtCTy3s+N3LfkCAwEAAQJAAOso8bEqBuYkqWolHpw3
s1+Zp0xDZklnwQkk+blO2f4VokFHVf0EheG45KkQAfIZK0GnxK5v5X9vQb6PtX2z
wQIhAPM7hpE0DwHHZbT+zG8cEoVazDNZdPpjU1wJ7HKrPu2DAiEA3ZyxB7KhSvn1
bA9UURccYmn7ppHJ3FQogZhTY7v8sokCIQDaxl00GV4g8ow6HxWfVHtLgQYVyxGi
gFRcHyPuPGvTiQIhAIAvdfmXoiAEAKLyH2VG9r5L6fDZzOVlK3kU4IJK1ahJAiBZ
3Ghuo1VGeF2hHpiFci0JHk3akGczRGwQ9+QXJFx+0g==
-----END RSA PRIVATE KEY-----`
Minimal Paramiko client example (Python):
import paramiko
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# Create a single underlying transport (one connection)
sock = client._get_socket("localhost", 2222, timeout=5)
transport = paramiko.Transport(sock)
transport.start_client()
# Load multiple keys and try them one by one on the same transport
keys = [
paramiko.RSAKey.from_private_key_file("/path/to/key1"),
paramiko.Ed25519Key.from_private_key_file("/path/to/key2"),
]
for key in keys:
try:
transport.auth_publickey("test", key)
print(f"Authenticated with key {key.get_name()}")
break
except Exception as e:
print("Auth failed with key:", e)
What did you see happen?
- The server logged the first key attempt, then closed the connection immediately.
- The second key attempt never reached the server.
- Paramiko retried authentication in the same connection by sending a new
SSH_MSG_SERVICE_REQUEST
, but the server did not accept it.
Server log example:
Listening on :2222
Key attempt from: test
handshake failed: ssh: handshake failed: ssh: no auth passed yet
Client output:
Auth failed with key: Authentication failed.
Auth failed with key: [Errno 104] Connection reset by peer
Additionally
- OpenSSH server works as expected when using
sshd
, multiple key attempts in one connection work fine.
This is because OpenSSH does not send anotherSSH_MSG_SERVICE_REQUEST
after the first authentication attempt; it simply proceeds to send additionalSSH_MSG_USERAUTH_REQUEST
packets for each key.
What did you expect to see?
- The server should accept the repeated
SSH_MSG_SERVICE_REQUEST
packets and allow multiple key attempts in the same session. - The connection should only be closed after all attempts fail (or after exceeding
MaxAuthTries
).
Metadata
Metadata
Assignees
Labels
BugReportIssues describing a possible bug in the Go implementation.Issues describing a possible bug in the Go implementation.NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.