Skip to content

x/crypto/ssh: server closes connection after first failed key, does not handle repeated SSH_MSG_SERVICE_REQUEST #75268

@9init

Description

@9init

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 another SSH_MSG_SERVICE_REQUEST after the first authentication attempt; it simply proceeds to send additional SSH_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

No one assigned

    Labels

    BugReportIssues 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.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions