ssh

package
v0.0.0-...-5cb2dda Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 5, 2026 License: MIT Imports: 27 Imported by: 0

Documentation

Overview

Package ssh provides the core SSH2 protocol implementation for Microsoft Dev Tunnels.

This package implements the SSH2 protocol over any bidirectional stream (not just TCP), supporting session management, channel multiplexing, key exchange, authentication, and session reconnection.

Architecture

The central type is Session, which manages the connection lifecycle and message dispatch. Sessions are created via ClientSession (for initiating connections) or ServerSession (for accepting connections). Both embed Session and add role-specific behavior.

Sessions operate over any io.ReadWriteCloser, enabling SSH over TCP, named pipes, WebSockets, or in-memory pipes for testing.

Key Exchange and Encryption

Algorithm negotiation follows RFC 4253. Supported algorithms are configured via SessionConfig and negotiated during the initial key exchange. The library supports ECDH (P-256, P-384, P-521), DH (group14, group16), AES-GCM, AES-CTR, AES-CBC, and HMAC-SHA2 variants.

A nil entry in an algorithm list means "none" (no security), useful for testing or trusted network scenarios.

Channel Multiplexing

Multiple logical channels can be multiplexed over a single session. Channels support bidirectional data transfer with flow control (window management). Use Session.OpenChannel to create channels and Session.AcceptChannel to accept incoming channel requests.

Authentication

Authentication is event-driven via the [Session.OnAuthenticating] callback. Supported methods include password, public key, and keyboard-interactive.

Reconnection

Sessions support transparent reconnection over a new stream without losing channel state. This is negotiated via protocol extensions and enabled automatically when both sides support it.

Services

Services extend session functionality. They are activated on-demand when a matching service request, channel type, or session request is received. The Service interface defines the contract for pluggable services.

Index

Constants

View Source
const (
	// DefaultMaxPacketSize is the default maximum packet size for channel data (32 KB).
	DefaultMaxPacketSize uint32 = 32 * 1024

	// DefaultMaxWindowSize is the default maximum window size for channel flow control (1 MB).
	DefaultMaxWindowSize uint32 = DefaultMaxPacketSize * 32
)
View Source
const (
	ExtensionServerSignatureAlgorithms = "server-sig-algs"
	ExtensionOpenChannelRequest        = "open-channel-request@microsoft.com"
	ExtensionSessionReconnect          = "session-reconnect@microsoft.com"
	ExtensionSessionLatency            = "session-latency@microsoft.com"
)

Protocol extension name constants.

The @microsoft.com domain is used for all Dev Tunnels SSH custom extensions. This matches the C# and TypeScript implementations exactly, and is the domain expected by the Dev Tunnels relay service. Extensions are negotiated via RFC 8308 ext-info messages during key exchange.

View Source
const (
	ExtensionRequestInitialChannelRequest  = "initial-channel-request@microsoft.com"
	ExtensionRequestKeepAlive              = "keepalive@openssh.com"
	ExtensionRequestEnableSessionReconnect = "enable-session-reconnect@microsoft.com"
)

Extension request type constants.

Uses the same @microsoft.com domain as the extension names above. The keepalive extension uses @openssh.com for OpenSSH compatibility.

View Source
const (
	AlgoKexEcdhNistp521 = "ecdh-sha2-nistp521"
	AlgoKexEcdhNistp384 = "ecdh-sha2-nistp384"
	AlgoKexEcdhNistp256 = "ecdh-sha2-nistp256"
	AlgoKexDHGroup16    = "diffie-hellman-group16-sha512"
	AlgoKexDHGroup14    = "diffie-hellman-group14-sha256"
	AlgoKexNone         = "none"

	AlgoPKRsaSha512     = "rsa-sha2-512"
	AlgoPKRsaSha256     = "rsa-sha2-256"
	AlgoPKEcdsaSha2P384 = "ecdsa-sha2-nistp384"
	AlgoPKEcdsaSha2P256 = "ecdsa-sha2-nistp256"
	AlgoPKEcdsaSha2P521 = "ecdsa-sha2-nistp521"
	AlgoPKNone          = "none"

	AlgoEncAes256Gcm = "aes256-gcm@openssh.com"
	AlgoEncAes256Cbc = "aes256-cbc"
	AlgoEncAes256Ctr = "aes256-ctr"
	AlgoEncNone      = "none"

	AlgoHmacSha512Etm = "hmac-sha2-512-etm@openssh.com"
	AlgoHmacSha256Etm = "hmac-sha2-256-etm@openssh.com"
	AlgoHmacSha512    = "hmac-sha2-512"
	AlgoHmacSha256    = "hmac-sha2-256"
	AlgoHmacNone      = "none"

	AlgoCompNone = "none"
)

Well-known algorithm names used in configuration.

View Source
const (
	AuthMethodNone                = "none"
	AuthMethodPassword            = "password"
	AuthMethodPublicKey           = "publickey"
	AuthMethodKeyboardInteractive = "keyboard-interactive"
	AuthMethodHostBased           = "hostbased"
)

Well-known authentication method names.

View Source
const (

	// TraceEventUnknownError is reported for unexpected errors.
	TraceEventUnknownError = baseEventID + 0
	// TraceEventStreamReadError is reported when reading from the stream fails.
	TraceEventStreamReadError = baseEventID + 1
	// TraceEventStreamWriteError is reported when writing to the stream fails.
	TraceEventStreamWriteError = baseEventID + 2
	// TraceEventStreamCloseError is reported when closing the stream fails.
	TraceEventStreamCloseError = baseEventID + 3
	// TraceEventSendMessageFailed is reported when sending a message fails.
	TraceEventSendMessageFailed = baseEventID + 4
	// TraceEventReceiveMessageFailed is reported when receiving a message fails.
	TraceEventReceiveMessageFailed = baseEventID + 5
	// TraceEventHandleMessageFailed is reported when handling a message fails.
	TraceEventHandleMessageFailed = baseEventID + 6
	// TraceEventServerAuthenticationFailed is reported when server authentication fails.
	TraceEventServerAuthenticationFailed = baseEventID + 7
	// TraceEventClientAuthenticationFailed is reported when client authentication fails.
	TraceEventClientAuthenticationFailed = baseEventID + 8
	// TraceEventAuthenticationError is reported for authentication errors.
	TraceEventAuthenticationError = baseEventID + 9
	// TraceEventChannelWindowAdjustFailed is reported when a channel window adjust fails.
	TraceEventChannelWindowAdjustFailed = baseEventID + 10
	// TraceEventSessionReconnectInitFailed is reported when reconnect initialization fails.
	TraceEventSessionReconnectInitFailed = baseEventID + 20
	// TraceEventServerSessionReconnectFailed is reported when server reconnect fails.
	TraceEventServerSessionReconnectFailed = baseEventID + 21
	// TraceEventClientSessionReconnectFailed is reported when client reconnect fails.
	TraceEventClientSessionReconnectFailed = baseEventID + 22
	// TraceEventSessionRequestFailed is reported when a session request fails.
	TraceEventSessionRequestFailed = baseEventID + 23
	// TraceEventChannelRequestFailed is reported when a channel request fails.
	TraceEventChannelRequestFailed = baseEventID + 24
	// TraceEventChannelCloseFailed is reported when closing a channel fails.
	TraceEventChannelCloseFailed = baseEventID + 25
	// TraceEventKeepAliveFailed is reported when a keep-alive fails.
	TraceEventKeepAliveFailed = baseEventID + 62
	// TraceEventKeepAliveResponseNotReceived is reported when keep-alive response times out.
	TraceEventKeepAliveResponseNotReceived = baseEventID + 64

	// TraceEventProtocolVersion is reported after version exchange.
	TraceEventProtocolVersion = baseEventID + 100
	// TraceEventSendingMessage is reported when sending a non-channel-data message.
	TraceEventSendingMessage = baseEventID + 101
	// TraceEventReceivingMessage is reported when receiving a non-channel-data message.
	TraceEventReceivingMessage = baseEventID + 102
	// TraceEventSendingChannelData is reported when sending channel data (TraceChannelData only).
	TraceEventSendingChannelData = baseEventID + 103
	// TraceEventReceivingChannelData is reported when receiving channel data (TraceChannelData only).
	TraceEventReceivingChannelData = baseEventID + 104
	// TraceEventSessionEncrypted is reported after key exchange completes and encryption is active.
	TraceEventSessionEncrypted = baseEventID + 110
	// TraceEventSessionAuthenticating is reported when authentication starts.
	TraceEventSessionAuthenticating = baseEventID + 111
	// TraceEventSessionAuthenticated is reported when authentication succeeds.
	TraceEventSessionAuthenticated = baseEventID + 112
	// TraceEventSessionClosing is reported when the session is closing.
	TraceEventSessionClosing = baseEventID + 113
	// TraceEventSessionConnecting is reported when the session is connecting.
	TraceEventSessionConnecting = baseEventID + 114
	// TraceEventChannelOpened is reported when a channel is successfully opened.
	TraceEventChannelOpened = baseEventID + 120
	// TraceEventChannelOpenFailed is reported when a channel open fails.
	TraceEventChannelOpenFailed = baseEventID + 121
	// TraceEventChannelClosed is reported when a channel is closed.
	TraceEventChannelClosed = baseEventID + 123
	// TraceEventSessionDisconnected is reported when the session is disconnected.
	TraceEventSessionDisconnected = baseEventID + 160
	// TraceEventClientSessionReconnecting is reported when a client is reconnecting.
	TraceEventClientSessionReconnecting = baseEventID + 161
	// TraceEventServerSessionReconnecting is reported when a server is reconnecting.
	TraceEventServerSessionReconnecting = baseEventID + 162
	// TraceEventAlgorithmNegotiation is reported during algorithm negotiation.
	TraceEventAlgorithmNegotiation = baseEventID + 170
)

SSHTraceEventID constants match the C#/TypeScript implementations. Event IDs use a base of 9000.

View Source
const (
	// AlgoKeyRsa is the SSH key algorithm name for RSA keys.
	AlgoKeyRsa = "ssh-rsa"
)

KeyAlgorithmName constants for SSH key types.

View Source
const AuthServiceName = "ssh-userauth"

AuthServiceName is the SSH service name for user authentication.

View Source
const ConnectionServiceName = "ssh-connection"

ConnectionServiceName is the service name requested during auth.

Variables

View Source
var (
	ErrSessionClosed    = errors.New("session is closed")
	ErrClosed           = errors.New("resource is closed")
	ErrNotConnected     = errors.New("session is not connected")
	ErrNotAuthenticated = errors.New("session is not authenticated")
)

Sentinel errors for common SSH session states.

Functions

func PipeSession

func PipeSession(ctx context.Context, sessionA, sessionB *Session) error

PipeSession pipes two sessions together bidirectionally. When a session request arrives on one session, it is forwarded to the other. When a channel is opened remotely on one session, a corresponding channel is opened on the other session with the same type, and the two channels are piped together (including data, extended data, EOF, and close events). When one session closes, the other is closed too.

PipeSession blocks until one session closes or the context is cancelled.

func WaitUntilReconnectEnabled

func WaitUntilReconnectEnabled(ctx context.Context, sessions ...*Session) error

WaitUntilReconnectEnabled polls until both sides of a session pair have reconnect extensions negotiated and enabled, AND both protocols have IncomingMessagesHaveReconnectInfo set (meaning each side has received the other's enable-reconnect message and message caching is active). Used in tests for initial setup with kex:none (where enableReconnect is called manually rather than via extension info exchange).

Types

type AuthenticatingEventArgs

type AuthenticatingEventArgs struct {
	AuthenticationType AuthenticationType
	Username           string
	Password           string
	PublicKey          KeyPair
	ClientHostname     string
	ClientUsername     string
	InfoRequest        *messages.AuthenticationInfoRequestMessage
	InfoResponse       *messages.AuthenticationInfoResponseMessage
	Ctx                context.Context

	// AuthenticationResult should be set by the event handler.
	// A non-nil value indicates successful authentication.
	AuthenticationResult interface{}
}

AuthenticatingEventArgs contains arguments for session authentication events.

type AuthenticationType

type AuthenticationType int

AuthenticationType indicates the type of authentication being requested by an SSH client or server.

const (
	// AuthClientNone indicates the client is authenticating without credentials,
	// with only a username, or is checking what methods the server supports.
	AuthClientNone AuthenticationType = 0

	// AuthClientHostBased indicates the client is authenticating with a host public key.
	AuthClientHostBased AuthenticationType = 1

	// AuthClientPassword indicates the client is authenticating with username and password.
	AuthClientPassword AuthenticationType = 2

	// AuthClientPublicKeyQuery indicates the client is querying whether a public key
	// would be accepted, without proving possession of the private key.
	AuthClientPublicKeyQuery AuthenticationType = 3

	// AuthClientPublicKey indicates the client is authenticating with a public key,
	// including a signature proving possession of the private key.
	AuthClientPublicKey AuthenticationType = 4

	// AuthClientInteractive indicates the client is authenticating via
	// keyboard-interactive prompts.
	AuthClientInteractive AuthenticationType = 5

	// AuthServerPublicKey indicates the server is authenticating with its host key.
	AuthServerPublicKey AuthenticationType = 10
)

type Channel

type Channel struct {
	// ChannelType is the type string for this channel (e.g., "session").
	ChannelType string

	// ChannelID is the local channel ID.
	ChannelID uint32

	// RemoteChannelID is the remote side's channel ID.
	RemoteChannelID uint32

	// MaxWindowSize is the local maximum receive window size.
	MaxWindowSize uint32

	// MaxPacketSize is the local maximum packet size.
	MaxPacketSize uint32

	// OnDataReceived is called when data is received on this channel.
	// The handler must call AdjustWindow(len(data)) when done processing.
	OnDataReceived func(data []byte)

	// OnExtendedDataReceived is called when extended data (e.g. stderr) is received.
	// If nil, extended data falls through to OnDataReceived for backward compatibility.
	// The handler must call AdjustWindow(len(data)) when done processing.
	OnExtendedDataReceived func(dataType SSHExtendedDataType, data []byte)

	// OnClosed is called when the channel is closed.
	OnClosed func(*ChannelClosedEventArgs)

	// OnEof is called when an EOF message is received from the remote side.
	OnEof func()

	// OnRequest is called when a channel request is received.
	OnRequest func(*RequestEventArgs)
	// contains filtered or unexported fields
}

Channel represents a single SSH channel within a session. Channels are multiplexed over a single SSH connection and support bidirectional data transfer with flow control.

func (*Channel) AdjustWindow

func (c *Channel) AdjustWindow(messageLength uint32)

AdjustWindow is called by the data receiver after processing received data. It sends a WindowAdjust message to the remote side when more than 50% of the window has been consumed.

func (*Channel) Close

func (c *Channel) Close() error

Close closes the channel by sending EOF (if not already sent) and Close messages. It waits for the remote side to acknowledge the close.

func (*Channel) CloseWithContext

func (c *Channel) CloseWithContext(ctx context.Context) error

CloseWithContext closes the channel with context support.

func (*Channel) CloseWithSignal

func (c *Channel) CloseWithSignal(ctx context.Context, signal string, errorMessage string) error

CloseWithSignal closes the channel with a signal name and error message. The signal info is propagated to the remote side via OnClosed callback.

func (*Channel) CloseWithStatus

func (c *Channel) CloseWithStatus(ctx context.Context, exitStatus uint32) error

CloseWithStatus closes the channel with an exit status code. The exit status is propagated to the remote side via OnClosed callback.

func (*Channel) IsClosed

func (c *Channel) IsClosed() bool

IsClosed returns true if the channel has been closed.

func (*Channel) Metrics

func (c *Channel) Metrics() *ChannelMetrics

Metrics returns a pointer to the channel's metrics counters.

func (*Channel) OpenConfirmationMessage

func (c *Channel) OpenConfirmationMessage() *messages.ChannelOpenConfirmationMessage

OpenConfirmationMessage returns the message that confirmed opening the channel. It is non-nil after the channel open is confirmed.

func (*Channel) OpenMessage

func (c *Channel) OpenMessage() *messages.ChannelOpenMessage

OpenMessage returns the message that requested opening the channel. It is non-nil after the channel is opened.

func (*Channel) Pipe

func (c *Channel) Pipe(ctx context.Context, target *Channel) error

Pipe relays data bidirectionally between this channel and the target channel. When one channel closes, the other is also closed. Pipe blocks until one of the channels is closed.

All handlers are installed synchronously in the calling goroutine before any events can be missed. Close handlers are installed atomically (read old + write new under a single lock) to prevent a race where the channel closes between snapshotting the old handler and installing the new one.

func (*Channel) Request

func (c *Channel) Request(ctx context.Context, msg *messages.ChannelRequestMessage) (bool, error)

Request sends a channel request and waits for a success/failure response. Returns true if the request was accepted, false otherwise.

func (*Channel) Send

func (c *Channel) Send(ctx context.Context, data []byte) error

Send sends data on the channel. It blocks if the remote window is exhausted and resumes when a WindowAdjust message is received. Sending empty/nil data sends an EOF message. Send is safe for concurrent use; multiple sends are serialized to prevent data interleaving across multi-packet messages.

func (*Channel) SendExtendedData

func (c *Channel) SendExtendedData(ctx context.Context, dataType SSHExtendedDataType, data []byte) error

SendExtendedData sends extended data (e.g. stderr) on the channel. It respects flow control the same way as Send.

func (*Channel) SendSignal

func (c *Channel) SendSignal(ctx context.Context, signalName string) error

SendSignal sends a standalone signal channel request to the remote side. The signalName should be a signal name like "TERM", "HUP", "INT", etc.

func (*Channel) SetClosedHandler

func (c *Channel) SetClosedHandler(handler func(*ChannelClosedEventArgs))

SetClosedHandler sets the OnClosed callback in a thread-safe manner.

func (*Channel) SetDataReceivedHandler

func (c *Channel) SetDataReceivedHandler(handler func([]byte))

SetDataReceivedHandler sets the OnDataReceived callback and flushes any data that was buffered while no handler was attached.

func (*Channel) SetEofHandler

func (c *Channel) SetEofHandler(handler func())

SetEofHandler sets the OnEof callback in a thread-safe manner.

func (*Channel) SetExtendedDataReceivedHandler

func (c *Channel) SetExtendedDataReceivedHandler(handler func(SSHExtendedDataType, []byte))

SetExtendedDataReceivedHandler sets the OnExtendedDataReceived callback and flushes any extended data that was buffered while no handler was attached.

func (*Channel) SetRequestHandler

func (c *Channel) SetRequestHandler(handler func(*RequestEventArgs))

SetRequestHandler sets the OnRequest callback in a thread-safe manner. Use this method instead of direct field assignment when the channel is already active, to avoid data races with the request handler goroutine.

type ChannelClosedEventArgs

type ChannelClosedEventArgs struct {
	ExitStatus   *uint32
	ExitSignal   string
	ErrorMessage string
	Err          error
}

ChannelClosedEventArgs contains arguments for channel closed events.

type ChannelError

type ChannelError struct {
	Reason messages.SSHChannelOpenFailureReason
	Msg    string
	Err    error
}

ChannelError represents an SSH channel error with a failure reason.

func (*ChannelError) Error

func (e *ChannelError) Error() string

func (*ChannelError) Unwrap

func (e *ChannelError) Unwrap() error

type ChannelMetrics

type ChannelMetrics struct {
	// contains filtered or unexported fields
}

ChannelMetrics tracks byte counters for a single SSH channel.

func (*ChannelMetrics) BytesReceived

func (m *ChannelMetrics) BytesReceived() int64

BytesReceived returns the total number of data bytes received on this channel.

func (*ChannelMetrics) BytesSent

func (m *ChannelMetrics) BytesSent() int64

BytesSent returns the total number of data bytes sent on this channel.

func (*ChannelMetrics) DroppedRequests

func (m *ChannelMetrics) DroppedRequests() int64

DroppedRequests returns the number of channel requests dropped due to a full queue.

type ChannelOpeningEventArgs

type ChannelOpeningEventArgs struct {
	Request            *messages.ChannelOpenMessage
	Channel            *Channel
	IsRemoteRequest    bool
	FailureReason      messages.SSHChannelOpenFailureReason
	FailureDescription string
	Ctx                context.Context

	// Payload contains the raw message payload bytes.
	// Services can use this to parse extended fields beyond the base ChannelOpenMessage.
	Payload []byte
}

ChannelOpeningEventArgs contains arguments for channel opening events.

type ClientCredentials

type ClientCredentials struct {
	Username   string
	Password   string
	PublicKeys []KeyPair

	// PasswordProvider is an optional callback that lazily provides a username and
	// password during authentication. When set, it is called instead of using the
	// static Username/Password fields for password authentication. This enables
	// interactive prompting or deferred credential loading, matching C#/TS
	// PasswordCredentialProvider behavior.
	PasswordProvider PasswordProvider

	// PrivateKeyProvider is an optional callback that provides a full key pair
	// (with private key material) given a public-key-only key pair. It is called
	// during authentication when a key in PublicKeys does not have HasPrivateKey() == true.
	// This enables deferred loading of private keys from secure storage.
	PrivateKeyProvider PrivateKeyProvider
}

ClientCredentials defines credentials for authenticating an SSH client session.

type ClientSession

type ClientSession struct {
	Session
}

ClientSession is an SSH client session that connects to a server.

func NewClientSession

func NewClientSession(config *SessionConfig) *ClientSession

NewClientSession creates a new SSH client session with the given configuration. If config is nil, a no-security configuration is used.

func (*ClientSession) Authenticate

func (cs *ClientSession) Authenticate(ctx context.Context, creds *ClientCredentials) (bool, error)

Authenticate authenticates the client session with the given credentials. It first verifies the server's host key (if any), then sends authentication credentials to the server. Returns true if authentication succeeds.

func (*ClientSession) AuthenticatePublicKeyQuery

func (cs *ClientSession) AuthenticatePublicKeyQuery(ctx context.Context, username string, key KeyPair) (bool, error)

AuthenticatePublicKeyQuery sends a public key query to check if the server would accept the given key, without proving possession of the private key.

func (*ClientSession) Reconnect

func (cs *ClientSession) Reconnect(ctx context.Context, newStream io.ReadWriteCloser) error

Reconnect reconnects a disconnected client session over a new stream. The new stream must connect to the same server (same host key). After reconnection, channels continue operating normally.

type ConnectionError

type ConnectionError struct {
	Reason messages.SSHDisconnectReason
	Msg    string
	Err    error
}

ConnectionError represents an SSH connection error with a disconnect reason.

func (*ConnectionError) Error

func (e *ConnectionError) Error() string

func (*ConnectionError) Unwrap

func (e *ConnectionError) Unwrap() error

type ContourUpdate

type ContourUpdate struct {
	Time          int64
	BytesSent     int
	BytesReceived int
	Latency       float32
}

ContourUpdate contains a single metrics update to be applied to the contour.

type EcdsaKeyPair

type EcdsaKeyPair struct {
	// contains filtered or unexported fields
}

EcdsaKeyPair implements KeyPair for ECDSA keys.

func NewEcdsaKeyPair

func NewEcdsaKeyPair(privateKey *ecdsa.PrivateKey) (*EcdsaKeyPair, error)

NewEcdsaKeyPair creates a new EcdsaKeyPair from an existing crypto/ecdsa private key.

func NewEcdsaKeyPairFromPublicKey

func NewEcdsaKeyPairFromPublicKey(pubKey *ecdsa.PublicKey) (*EcdsaKeyPair, error)

NewEcdsaKeyPairFromPublicKey creates a public-key-only EcdsaKeyPair from a crypto/ecdsa public key.

func (*EcdsaKeyPair) Comment

func (k *EcdsaKeyPair) Comment() string

Comment returns the key comment.

func (*EcdsaKeyPair) GetPublicKeyBytes

func (k *EcdsaKeyPair) GetPublicKeyBytes() ([]byte, error)

GetPublicKeyBytes returns the ECDSA public key in SSH wire format. Format: [string algorithm][string curve-name][binary 0x04||X||Y]

func (*EcdsaKeyPair) HasPrivateKey

func (k *EcdsaKeyPair) HasPrivateKey() bool

HasPrivateKey returns true if this key pair includes a private key.

func (*EcdsaKeyPair) KeyAlgorithmName

func (k *EcdsaKeyPair) KeyAlgorithmName() string

KeyAlgorithmName returns the SSH algorithm name (e.g., "ecdsa-sha2-nistp256").

func (*EcdsaKeyPair) PrivateKey

func (k *EcdsaKeyPair) PrivateKey() *ecdsa.PrivateKey

PrivateKey returns the underlying crypto/ecdsa private key, or nil.

func (*EcdsaKeyPair) PublicKey

func (k *EcdsaKeyPair) PublicKey() *ecdsa.PublicKey

PublicKey returns the underlying crypto/ecdsa public key.

func (*EcdsaKeyPair) SetComment

func (k *EcdsaKeyPair) SetComment(comment string)

SetComment sets the key comment.

func (*EcdsaKeyPair) SetPublicKeyBytes

func (k *EcdsaKeyPair) SetPublicKeyBytes(data []byte) error

SetPublicKeyBytes imports an ECDSA public key from SSH wire format bytes.

func (*EcdsaKeyPair) Sign

func (k *EcdsaKeyPair) Sign(data []byte) ([]byte, error)

Sign signs data using the ECDSA private key. The signature is returned in SSH format: [mpint r][mpint s].

func (*EcdsaKeyPair) Verify

func (k *EcdsaKeyPair) Verify(data, signature []byte) (bool, error)

Verify verifies an ECDSA signature over data. The signature is expected in SSH format: [mpint r][mpint s].

type KeyPair

type KeyPair interface {
	// KeyAlgorithmName returns the SSH key algorithm name
	// (e.g., "ssh-rsa", "ecdsa-sha2-nistp256").
	KeyAlgorithmName() string

	// HasPrivateKey returns true if the key pair includes a private key for signing.
	HasPrivateKey() bool

	// GetPublicKeyBytes returns the public key in SSH wire format.
	// The format is: [string algorithm-name][key-type-specific data].
	GetPublicKeyBytes() ([]byte, error)

	// SetPublicKeyBytes imports a public key from SSH wire format bytes.
	// After calling this, HasPrivateKey returns false.
	SetPublicKeyBytes(data []byte) error

	// Comment returns the key comment string.
	Comment() string

	// SetComment sets the key comment string.
	SetComment(comment string)
}

KeyPair represents an SSH key pair with optional private key for signing.

func GenerateKeyPair

func GenerateKeyPair(algorithmName string) (KeyPair, error)

GenerateKeyPair generates a new key pair for the specified algorithm using a default key size. Supported algorithm names:

  • "rsa-sha2-256": RSA 2048-bit with SHA-256 signing
  • "rsa-sha2-512": RSA 4096-bit with SHA-512 signing
  • "ecdsa-sha2-nistp256": ECDSA with P-256 curve
  • "ecdsa-sha2-nistp384": ECDSA with P-384 curve
  • "ecdsa-sha2-nistp521": ECDSA with P-521 curve

func GenerateKeyPairWithSize

func GenerateKeyPairWithSize(algorithmName string, keySizeInBits int) (KeyPair, error)

GenerateKeyPairWithSize generates a new key pair for the specified algorithm with an explicit key size in bits. For RSA algorithms, keySizeInBits controls the RSA key length (e.g., 2048, 4096). For ECDSA algorithms, keySizeInBits is ignored (the curve determines the key size).

func KeyPairFromPublicKeyBytes

func KeyPairFromPublicKeyBytes(data []byte) (KeyPair, error)

KeyPairFromPublicKeyBytes creates a KeyPair from SSH wire-format public key bytes. The algorithm name is read from the beginning of the data.

type MessageHandler

type MessageHandler func(payload []byte) error

MessageHandler is a callback that handles a custom SSH message type. The payload parameter contains the full raw message including the type byte.

type MultiChannelStream

type MultiChannelStream struct {
	// OnChannelOpening is called when a channel open request is received.
	OnChannelOpening func(*ChannelOpeningEventArgs)

	// OnClosed is called when the stream is closed.
	OnClosed func(*SessionClosedEventArgs)

	// ChannelMaxWindowSize is the maximum window size for channels.
	// Zero uses the default (1 MB).
	ChannelMaxWindowSize uint32
	// contains filtered or unexported fields
}

MultiChannelStream provides lightweight channel multiplexing over a single transport stream without encryption. It is suitable for scenarios where the transport is already secure (e.g., TLS).

MultiChannelStream uses an underlying SSH session configured with "none" algorithms (no encryption, no HMAC, no key exchange) to provide channel multiplexing, flow control, and message framing without cryptographic overhead.

func NewMultiChannelStream

func NewMultiChannelStream(transportStream io.ReadWriteCloser, isClient bool) *MultiChannelStream

NewMultiChannelStream creates a new MultiChannelStream wrapping the given transport stream. The isClient parameter indicates whether this end acts as the SSH client (true) or server (false).

func (*MultiChannelStream) AcceptChannel

func (mcs *MultiChannelStream) AcceptChannel(ctx context.Context, channelType string) (*Channel, error)

AcceptChannel waits for and accepts an incoming channel with the given type. If channelType is empty, any channel type is accepted.

func (*MultiChannelStream) AcceptStream

func (mcs *MultiChannelStream) AcceptStream(ctx context.Context, channelType string) (*Stream, error)

AcceptStream waits for an incoming channel and wraps it as an io.ReadWriteCloser.

func (*MultiChannelStream) Close

func (mcs *MultiChannelStream) Close() error

Close closes the underlying session and transport stream.

func (*MultiChannelStream) Connect

func (mcs *MultiChannelStream) Connect(ctx context.Context) error

Connect establishes the multiplexed connection by performing SSH version exchange and key exchange (with "none" algorithms). This method is idempotent — calling it multiple times returns immediately after the first successful connection.

func (*MultiChannelStream) ConnectAndRunUntilClosed

func (mcs *MultiChannelStream) ConnectAndRunUntilClosed(ctx context.Context) error

ConnectAndRunUntilClosed connects and then blocks until the session is closed or the context is cancelled. This is useful for server-side code that needs to process channels until the connection ends.

func (*MultiChannelStream) IsClosed

func (mcs *MultiChannelStream) IsClosed() bool

IsClosed returns true if the stream has been closed.

func (*MultiChannelStream) OpenChannel

func (mcs *MultiChannelStream) OpenChannel(ctx context.Context, channelType string) (*Channel, error)

OpenChannel opens a new channel with the given channel type. If channelType is empty, "session" is used as the default.

func (*MultiChannelStream) OpenStream

func (mcs *MultiChannelStream) OpenStream(ctx context.Context, channelType string) (*Stream, error)

OpenStream opens a new channel and wraps it as an io.ReadWriteCloser.

func (*MultiChannelStream) Session

func (mcs *MultiChannelStream) Session() *Session

Session returns the underlying SSH session.

type PasswordProvider

type PasswordProvider func(ctx context.Context) (username string, password string, err error)

PasswordProvider is an optional callback that lazily provides a username and password during authentication. This enables interactive prompting or deferred credential loading. If the provider returns an error, the session is closed with DisconnectAuthCancelledByUser. If the provider returns empty username and password, password auth is skipped.

type PrivateKeyProvider

type PrivateKeyProvider func(ctx context.Context, publicKey KeyPair) (KeyPair, error)

PrivateKeyProvider is a callback that provides a full key pair (with private key) given a public-key-only key pair. It is called during authentication or key exchange when a key in the credentials list does not have private key material loaded. This enables deferred/lazy loading of private keys from secure storage (e.g., HSMs, key vaults, or encrypted files) rather than requiring all private keys in memory upfront.

type Progress

type Progress int

Progress represents connection progress events reported during session lifecycle. These match the C#/TS Progress enum values.

const (
	// ProgressOpeningSSHSessionConnection is reported when starting a new SSH session connection.
	ProgressOpeningSSHSessionConnection Progress = iota
	// ProgressOpenedSSHSessionConnection is reported after the SSH session connection is fully established.
	ProgressOpenedSSHSessionConnection
	// ProgressStartingProtocolVersionExchange is reported when starting the protocol version exchange.
	ProgressStartingProtocolVersionExchange
	// ProgressCompletedProtocolVersionExchange is reported after the protocol version exchange completes.
	ProgressCompletedProtocolVersionExchange
	// ProgressStartingKeyExchange is reported when starting the key exchange.
	ProgressStartingKeyExchange
	// ProgressCompletedKeyExchange is reported after the key exchange completes.
	ProgressCompletedKeyExchange
	// ProgressStartingSessionAuthentication is reported when starting session authentication.
	ProgressStartingSessionAuthentication
	// ProgressCompletedSessionAuthentication is reported after session authentication completes.
	ProgressCompletedSessionAuthentication
)

type ReconnectError

type ReconnectError struct {
	Reason messages.SSHReconnectFailureReason
	Msg    string
	Err    error
}

ReconnectError represents an SSH reconnection error with a failure reason.

func (*ReconnectError) Error

func (e *ReconnectError) Error() string

func (*ReconnectError) Unwrap

func (e *ReconnectError) Unwrap() error

type ReconnectableSessions

type ReconnectableSessions struct {
	// contains filtered or unexported fields
}

ReconnectableSessions is a thread-safe collection of disconnected server sessions awaiting reconnection. It must be shared across all ServerSession instances that should be able to reconnect to each other.

func NewReconnectableSessions

func NewReconnectableSessions() *ReconnectableSessions

NewReconnectableSessions creates a new empty collection.

func (*ReconnectableSessions) Add

Add adds a session to the collection if it isn't already present. Call this after a session is connected and reconnect is enabled to make it available for reconnection after disconnect.

type RequestEventArgs

type RequestEventArgs struct {
	RequestType  string
	Request      messages.Message
	IsAuthorized bool
	Ctx          context.Context

	// Principal holds the authenticated identity from the session.
	// This is populated from Session.Principal when the request is dispatched.
	Principal interface{}

	// Payload contains the raw message payload bytes.
	// Services can use this to parse extended fields beyond the base message.
	Payload []byte

	// ResponseMessage, if set, will be sent instead of the plain success message
	// when IsAuthorized is true. This allows services to send custom response data
	// (e.g., allocated port in port forwarding).
	ResponseMessage messages.Message

	// ResponseHandled indicates the service has already sent the response.
	// When true, the session will not send any automatic response.
	ResponseHandled bool
}

RequestEventArgs contains arguments for session or channel request events.

type RsaKeyPair

type RsaKeyPair struct {
	// contains filtered or unexported fields
}

RsaKeyPair implements KeyPair for RSA keys. It supports signing with SHA-256 (rsa-sha2-256) or SHA-512 (rsa-sha2-512).

func NewRsaKeyPair

func NewRsaKeyPair(privateKey *rsa.PrivateKey, algorithmName string) (*RsaKeyPair, error)

NewRsaKeyPair creates a new RsaKeyPair from an existing crypto/rsa private key. algorithmName should be "rsa-sha2-256" or "rsa-sha2-512" to determine the hash.

func NewRsaKeyPairFromPublicKey

func NewRsaKeyPairFromPublicKey(pubKey *rsa.PublicKey) *RsaKeyPair

NewRsaKeyPairFromPublicKey creates a public-key-only RsaKeyPair from a crypto/rsa public key.

func (*RsaKeyPair) Comment

func (k *RsaKeyPair) Comment() string

Comment returns the key comment.

func (*RsaKeyPair) GetPublicKeyBytes

func (k *RsaKeyPair) GetPublicKeyBytes() ([]byte, error)

GetPublicKeyBytes returns the RSA public key in SSH wire format. Format: [string "ssh-rsa"][mpint e][mpint n]

func (*RsaKeyPair) HasPrivateKey

func (k *RsaKeyPair) HasPrivateKey() bool

HasPrivateKey returns true if this key pair includes a private key.

func (*RsaKeyPair) KeyAlgorithmName

func (k *RsaKeyPair) KeyAlgorithmName() string

KeyAlgorithmName returns "ssh-rsa", the SSH key algorithm identifier for RSA.

func (*RsaKeyPair) PrivateKey

func (k *RsaKeyPair) PrivateKey() *rsa.PrivateKey

PrivateKey returns the underlying crypto/rsa private key, or nil.

func (*RsaKeyPair) PublicKey

func (k *RsaKeyPair) PublicKey() *rsa.PublicKey

PublicKey returns the underlying crypto/rsa public key.

func (*RsaKeyPair) SetComment

func (k *RsaKeyPair) SetComment(comment string)

SetComment sets the key comment.

func (*RsaKeyPair) SetPublicKeyBytes

func (k *RsaKeyPair) SetPublicKeyBytes(data []byte) error

SetPublicKeyBytes imports an RSA public key from SSH wire format bytes. Accepts algorithm names: "ssh-rsa", "rsa-sha2-256", "rsa-sha2-512".

func (*RsaKeyPair) Sign

func (k *RsaKeyPair) Sign(data []byte) ([]byte, error)

Sign signs data using the RSA private key with PKCS#1 v1.5 padding. The hash algorithm (SHA-256 or SHA-512) is determined by the algorithm name used when creating the key pair.

func (*RsaKeyPair) Verify

func (k *RsaKeyPair) Verify(data, signature []byte) (bool, error)

Verify verifies an RSA PKCS#1 v1.5 signature over data.

type SSHExtendedDataType

type SSHExtendedDataType uint32

SSHExtendedDataType represents the type code for SSH extended data.

const (
	// ExtendedDataStderr is the standard type code for stderr data.
	ExtendedDataStderr SSHExtendedDataType = 1
)

type SSHProtocol

type SSHProtocol struct {

	// SendSequence tracks the number of messages sent (64-bit for reconnect tracking).
	// The lower 32 bits are used for HMAC computation per SSH spec.
	SendSequence uint64
	// ReceiveSequence tracks the number of messages received (64-bit for reconnect tracking).
	// Atomic because it is written by the dispatch loop and read by sendMessage via LastIncomingSequence.
	ReceiveSequence uint64 // atomic

	// Reconnect info flags. When enabled, extra bytes are appended to/stripped from
	// each packet for sequence acknowledgment and optional latency measurement.
	// These are atomic int32 (0/1) to allow concurrent access from send and dispatch goroutines.
	OutgoingMessagesHaveReconnectInfo int32 // atomic; 0 = false, 1 = true
	IncomingMessagesHaveReconnectInfo int32 // atomic; 0 = false, 1 = true
	OutgoingMessagesHaveLatencyInfo   int32 // atomic; 0 = false, 1 = true
	IncomingMessagesHaveLatencyInfo   int32 // atomic; 0 = false, 1 = true

	// BytesSent and BytesReceived track cumulative wire bytes for key rotation threshold.
	// These are atomic to allow concurrent send/receive access.
	// Reset to 0 after successful key exchange in activateNewKeys.
	BytesSent     uint64 // atomic
	BytesReceived uint64 // atomic
	// contains filtered or unexported fields
}

SSHProtocol handles binary SSH packet framing on a stream.

Wire packet format (RFC 4253):

[uint32 packet_length] [byte padding_length] [payload] [random padding] [MAC]

Where packet_length = padding_length_size(1) + payload_length + padding_length.

Three encryption modes are supported:

  • Standard: HMAC on plaintext, then encrypt full packet (including length)
  • EtM (encrypt-then-MAC): encrypt without length, then HMAC on ciphertext
  • GCM (AEAD): encrypt without length, authentication tag from cipher

func (*SSHProtocol) GetSentMessages

func (p *SSHProtocol) GetSentMessages(startingSequenceNumber uint64) [][]byte

GetSentMessages retrieves cached sent messages starting from the given sequence number. Returns:

  • empty slice if the remote side is already up-to-date
  • nil if the cache doesn't go back far enough (messages were purged)
  • slice of payload bytes otherwise

Key exchange and disconnect messages are excluded since they cannot be retransmitted; a reconnected session will do key exchange separately.

func (*SSHProtocol) LastIncomingSequence

func (p *SSHProtocol) LastIncomingSequence() uint64

LastIncomingSequence returns the sequence number of the last received message. Returns 0 if no messages have been received yet (avoids uint64 underflow).

func (*SSHProtocol) SetEncryption

func (p *SSHProtocol) SetEncryption(
	encryptCipher, decryptCipher algorithms.Cipher,
	signer algorithms.MessageSigner,
	verifier algorithms.MessageVerifier,
)

SetEncryption activates encryption and HMAC on this protocol instance. Called after key exchange completes. Pass nil to disable encryption. Acquires sendMu to prevent races with sendMessage reading send-side fields.

type SecureStream

type SecureStream struct {
	// OnAuthenticating is called when authentication credentials need to be
	// verified. For a client, it is called to verify the server host key. For
	// a server, it is called to verify the client's credentials.
	//
	// The handler must set AuthenticationResult to a non-nil value to accept.
	OnAuthenticating func(*AuthenticatingEventArgs)

	// OnDisconnected is called when the session disconnects while reconnection
	// is enabled. After this fires, the client application should call
	// Reconnect() with a new transport stream. The server handles reconnections
	// automatically during the session handshake.
	OnDisconnected func()

	// OnClosed is called when the underlying SSH session is closed.
	OnClosed func(*SessionClosedEventArgs)
	// contains filtered or unexported fields
}

SecureStream establishes an end-to-end encrypted, two-way authenticated data stream over an underlying transport stream, using the SSH protocol but providing a simplified interface that is limited to a single duplex stream (channel).

This type is a complement to MultiChannelStream, which provides only the channel-multiplexing functions of SSH without encryption.

To establish a secure connection, the two sides first establish an insecure transport stream over a pipe, socket, or anything else. Then they encrypt and authenticate the connection before beginning to send and receive data.

func NewSecureStreamClient

func NewSecureStreamClient(
	transportStream io.ReadWriteCloser,
	clientCredentials *ClientCredentials,
	enableReconnect bool,
) *SecureStream

NewSecureStreamClient creates a SecureStream over an underlying transport stream using client credentials. Set enableReconnect to true to enable SSH session reconnection.

func NewSecureStreamServer

func NewSecureStreamServer(
	transportStream io.ReadWriteCloser,
	serverCredentials *ServerCredentials,
	reconnectableSessions *ReconnectableSessions,
) *SecureStream

NewSecureStreamServer creates a SecureStream over an underlying transport stream using server credentials. Pass a non-nil reconnectableSessions collection to enable reconnection support.

func (*SecureStream) Close

func (ss *SecureStream) Close() error

Close closes the underlying SSH session and transport stream. Close implements io.Closer.

func (*SecureStream) Connect

func (ss *SecureStream) Connect(ctx context.Context) error

Connect initiates the SSH session over the transport stream. It performs version exchange, key exchange, authentication, and opens a channel. On success, Read and Write can be used to exchange data.

func (*SecureStream) IsClosed

func (ss *SecureStream) IsClosed() bool

IsClosed returns true if the secure stream has been closed.

func (*SecureStream) Read

func (ss *SecureStream) Read(p []byte) (int, error)

Read reads data from the secure stream. It blocks until data is available, the stream is closed, or an error occurs. Read implements io.Reader.

func (*SecureStream) Reconnect

func (ss *SecureStream) Reconnect(ctx context.Context, newTransportStream io.ReadWriteCloser) error

Reconnect reconnects a disconnected client session over a new transport stream. The new stream must connect to the same server (same host key). This method applies only to client-side secure streams.

func (*SecureStream) Session

func (ss *SecureStream) Session() *Session

Session returns the underlying SSH session.

func (*SecureStream) Write

func (ss *SecureStream) Write(p []byte) (int, error)

Write sends data on the secure stream. Write implements io.Writer.

type SequencedMessage

type SequencedMessage struct {
	Sequence uint64
	Payload  []byte // original payload (before reconnect info appended)
	SentTime int64  // timestamp in microseconds
}

SequencedMessage stores a sent message payload with its sequence number and timestamp for reconnection retransmission.

type ServerCredentials

type ServerCredentials struct {
	PublicKeys []KeyPair

	// PrivateKeyProvider is an optional callback that provides a full key pair
	// (with private key material) given a public-key-only key pair. It is called
	// during key exchange when the host key does not have HasPrivateKey() == true.
	// This enables deferred loading of private keys from secure storage.
	PrivateKeyProvider PrivateKeyProvider
}

ServerCredentials defines credentials for authenticating an SSH server session.

type ServerSession

type ServerSession struct {
	Session
	Credentials *ServerCredentials

	// OnClientAuthenticated is called when a client successfully authenticates.
	OnClientAuthenticated func()

	// OnReconnected is called after a client successfully reconnects to this session.
	OnReconnected func()

	// ReconnectableSessions is a shared collection of disconnected sessions
	// awaiting reconnection. Must be set before connecting if reconnection
	// support is desired. Multiple ServerSession instances can share the same
	// collection to enable reconnection across sessions.
	ReconnectableSessions *ReconnectableSessions
}

ServerSession is an SSH server session that accepts client connections.

func NewServerSession

func NewServerSession(config *SessionConfig) *ServerSession

NewServerSession creates a new SSH server session with the given configuration. If config is nil, a no-security configuration is used.

func (*ServerSession) SetClientAuthenticatedHandler

func (ss *ServerSession) SetClientAuthenticatedHandler(handler func())

SetClientAuthenticatedHandler sets the OnClientAuthenticated callback in a thread-safe manner. Use this method instead of direct field assignment when the session is already connected, to avoid data races with the dispatch goroutine.

func (*ServerSession) SetReconnectedHandler

func (ss *ServerSession) SetReconnectedHandler(handler func())

SetReconnectedHandler sets the OnReconnected callback in a thread-safe manner. Use this method instead of direct field assignment when the session is already connected, to avoid data races with the dispatch goroutine.

type Service

type Service interface {
	// OnSessionRequest is called when a session request is received that
	// matches this service's activation rule.
	OnSessionRequest(args *RequestEventArgs)

	// OnChannelOpening is called when a channel open request is received
	// that matches this service's channel type activation rule.
	OnChannelOpening(args *ChannelOpeningEventArgs)

	// OnChannelRequest is called when a channel request is received that
	// matches this service's channel request activation rule.
	OnChannelRequest(channel *Channel, args *RequestEventArgs)

	// Close is called when the session is closing to clean up resources.
	// Implements io.Closer.
	Close() error
}

Service is the interface that SSH services must implement. Services handle incoming requests and are activated on-demand based on activation rules (service request name, session request type, channel type, channel request type).

type ServiceActivation

type ServiceActivation struct {
	// ServiceRequest activates the service when a service request (MSG 5) is
	// received with the specified name. Only server-side.
	ServiceRequest string

	// SessionRequest activates the service when a session request (MSG 80) is
	// received with the specified request type.
	SessionRequest string

	// SessionRequests activates the service when a session request (MSG 80)
	// matches any of the specified request types.
	SessionRequests []string

	// ChannelType activates the service when a channel open request is received
	// for the specified channel type.
	ChannelType string

	// ChannelTypes activates the service when a channel open request matches
	// any of the specified channel types.
	ChannelTypes []string

	// ChannelRequest activates the service when a channel request is received
	// for the specified request type. If ChannelType is also set, both must match.
	ChannelRequest string
}

ServiceActivation defines activation rules for a service. Each field specifies a condition under which the service is activated. For services that respond to multiple triggers (e.g., port forwarding), use the plural fields (SessionRequests, ChannelTypes) which accept slices.

type ServiceFactory

type ServiceFactory func(session *Session, config interface{}) Service

ServiceFactory creates a new instance of a service for the given session. The config parameter is the value from SessionConfig.Services map (may be nil).

type ServiceRegistration

type ServiceRegistration struct {
	Activation ServiceActivation
	Factory    ServiceFactory
	Config     interface{}
}

ServiceRegistration combines activation rules with a factory function.

type Session

type Session struct {
	Config *SessionConfig

	// OnAuthenticating is called when authentication credentials need to be verified.
	OnAuthenticating func(*AuthenticatingEventArgs)

	// OnChannelOpening is called when a channel open request is received.
	OnChannelOpening func(*ChannelOpeningEventArgs)

	// OnClosed is called when the session is closed.
	OnClosed func(*SessionClosedEventArgs)

	// OnRequest is called when a session request is received.
	OnRequest func(*RequestEventArgs)

	// OnServiceActivated is called when a service is activated for the first time.
	OnServiceActivated func(Service)

	// OnKeepAliveSucceeded is called when a keep-alive response is received.
	// The argument is the consecutive success count.
	OnKeepAliveSucceeded func(int)

	// OnKeepAliveFailed is called when no keep-alive response is received within the timeout.
	// The argument is the consecutive failure count.
	OnKeepAliveFailed func(int)

	// OnDisconnected is called when the session transitions to a disconnected
	// (but not closed) state while reconnection is enabled. Applications can
	// call Reconnect() with a new stream to resume the session.
	OnDisconnected func()

	// OnReportProgress is called to report connection progress at key handshake stages.
	OnReportProgress func(Progress)

	// Principal holds the authenticated identity after successful authentication.
	// On the server side, this is populated from AuthenticatingEventArgs.AuthenticationResult.
	// It is nil before authentication completes.
	Principal interface{}

	// Trace is called for structured trace events during session operation.
	// Set this before calling Connect to receive all trace events.
	// The function is nil-safe — no overhead when not set.
	Trace TraceFunc

	// RemoteVersion contains parsed version info from the remote side
	// after the version exchange completes.
	RemoteVersion *VersionInfo

	// ProtocolExtensions from remote side (from ExtensionInfoMessage).
	ProtocolExtensions map[string]string

	// SessionID is the exchange hash from the first key exchange.
	// It is set once and never changes, even on re-exchange.
	SessionID []byte
	// contains filtered or unexported fields
}

Session is the base type for SSH client and server sessions. It manages the connection lifecycle, message dispatch, and channel multiplexing.

func (*Session) AcceptChannel

func (s *Session) AcceptChannel(ctx context.Context) (*Channel, error)

AcceptChannel waits for and accepts an incoming channel of any type.

func (*Session) AcceptChannelWithType

func (s *Session) AcceptChannelWithType(ctx context.Context, channelType string) (*Channel, error)

AcceptChannelWithType waits for and accepts an incoming channel of the specified type. If channelType is empty, any channel type is accepted.

Uses a broadcast notification pattern so that multiple goroutines waiting for different channel types are all woken when a new channel arrives. Each waiter checks the queue for its specific type, avoiding the livelock that would occur with a single-consumer notification channel.

func (*Session) ActivateService

func (s *Session) ActivateService(name string) Service

ActivateService activates a service by name. If the service is already activated, returns the existing instance. Returns nil if the service name is not registered.

func (*Session) Channels

func (s *Session) Channels() map[uint32]*Channel

Channels returns a snapshot (copy) of the active channels map. The returned map is safe to iterate without holding any lock.

func (*Session) Close

func (s *Session) Close() error

Close closes the session gracefully with DisconnectByApplication reason. Implements io.Closer. Returns nil; close errors are reported via OnClosed.

func (*Session) CloseWithError

func (s *Session) CloseWithError(reason messages.SSHDisconnectReason, msg string, err error) error

CloseWithError closes the session with a specific disconnect reason and a custom error that is propagated to channel OnClosed callbacks.

func (*Session) CloseWithReason

func (s *Session) CloseWithReason(ctx context.Context, reason messages.SSHDisconnectReason, msg string) error

CloseWithReason closes the session with a specific disconnect reason and message.

func (*Session) Connect

func (s *Session) Connect(ctx context.Context, stream io.ReadWriteCloser) error

Connect connects the session over the given stream. Both sides exchange SSH-2.0 version strings and key exchange init messages.

func (*Session) CreateReconnectToken

func (s *Session) CreateReconnectToken(previousSessionID, newSessionID []byte) ([]byte, error)

CreateReconnectToken generates an HMAC-based reconnect token from previous and new session IDs. The token proves knowledge of the old session ID without disclosing it, and prevents replay attacks by including the new session ID. Uses the dedicated reconnect HMAC signer (not the GCM cipher, which would race with the dispatch loop's packet encryption).

func (*Session) Done

func (s *Session) Done() <-chan struct{}

Done returns a channel that is closed when the session's dispatch loop exits. This can be used to derive contexts that are scoped to the session's lifetime.

func (*Session) GetService

func (s *Session) GetService(name string) Service

GetService returns the active service with the given name, or nil if not activated.

func (*Session) IsClosed

func (s *Session) IsClosed() bool

IsClosed returns true if the session has been closed.

func (*Session) IsConnected

func (s *Session) IsConnected() bool

IsConnected returns true if the session is currently connected.

func (*Session) Metrics

func (s *Session) Metrics() *SessionMetrics

Metrics returns a pointer to the session's metrics counters.

func (*Session) OpenChannel

func (s *Session) OpenChannel(ctx context.Context) (*Channel, error)

OpenChannel opens a new channel with the default "session" type.

func (*Session) OpenChannelWithMessage

func (s *Session) OpenChannelWithMessage(
	ctx context.Context,
	channelType string,
	buildMsg func(senderChannel, maxWindowSize, maxPacketSize uint32) messages.Message,
) (*Channel, error)

OpenChannelWithMessage opens a new channel by sending a custom channel open message. The buildMsg callback receives the allocated local channel ID, window size, and packet size, and returns the message to send. The message must serialize as a valid SSH_MSG_CHANNEL_OPEN (type 90) with matching SenderChannel, MaxWindowSize, and MaxPacketSize. This is used by services like port forwarding that need extended channel open data.

func (*Session) OpenChannelWithRequest

func (s *Session) OpenChannelWithRequest(
	ctx context.Context,
	openMsg *messages.ChannelOpenMessage,
	initialRequest *messages.ChannelRequestMessage,
) (*Channel, error)

OpenChannelWithRequest opens a channel and sends an initial channel request. If the open-channel-request protocol extension is negotiated, the request is bundled with the channel open to avoid an extra round-trip. Otherwise, the request is sent as a standard channel request after the channel is opened. Returns the opened channel. Returns an error if the channel open or the initial request fails.

func (*Session) OpenChannelWithType

func (s *Session) OpenChannelWithType(ctx context.Context, channelType string) (*Channel, error)

OpenChannelWithType opens a new channel with the given channel type.

func (*Session) Protocol

func (s *Session) Protocol() *SSHProtocol

Protocol returns the session's protocol layer. Used by reconnection logic to access sequence tracking and message cache.

func (*Session) Request

func (s *Session) Request(ctx context.Context, msg *messages.SessionRequestMessage) (bool, error)

Request sends a session request and waits for the response. Returns true if the request was accepted, false otherwise.

func (*Session) RequestService

func (s *Session) RequestService(serviceName string) error

RequestService sends a service request (SSH_MSG_SERVICE_REQUEST) to the remote side and waits for the service accept response. This is used by clients to request server-side service activation.

This is a convenience wrapper around RequestServiceContext with context.Background().

func (*Session) RequestServiceContext

func (s *Session) RequestServiceContext(ctx context.Context, serviceName string) error

RequestServiceContext sends a service request (SSH_MSG_SERVICE_REQUEST) to the remote side and waits for the service accept response, respecting the provided context for cancellation and timeouts. This is used by clients to request server-side service activation.

func (*Session) RequestWithPayload

func (s *Session) RequestWithPayload(ctx context.Context, msg messages.Message, wantReply bool) (bool, []byte, error)

RequestWithPayload sends a session request and returns both success and the raw response payload. This is useful for requests like tcpip-forward where the success response contains additional data (e.g., allocated port).

func (*Session) SendMessage

func (s *Session) SendMessage(msg messages.Message) error

SendMessage sends a message through the protocol layer.

func (*Session) SendRawMessage

func (s *Session) SendRawMessage(payload []byte) error

SendRawMessage sends a raw byte payload through the protocol layer. This is primarily used for testing (e.g., sending unknown message types).

func (*Session) Services

func (s *Session) Services() []Service

Services returns a snapshot of all currently activated services.

func (*Session) SetAuthenticatingHandler

func (s *Session) SetAuthenticatingHandler(handler func(*AuthenticatingEventArgs))

SetAuthenticatingHandler sets the OnAuthenticating callback in a thread-safe manner. Use this method instead of direct field assignment when the session is already connected, to avoid data races with the dispatch goroutine.

func (*Session) SetChannelOpeningHandler

func (s *Session) SetChannelOpeningHandler(handler func(*ChannelOpeningEventArgs))

SetChannelOpeningHandler sets the OnChannelOpening callback in a thread-safe manner. Use this method instead of direct field assignment when the session is already connected, to avoid data races with the dispatch goroutine.

func (*Session) SetClosedHandler

func (s *Session) SetClosedHandler(handler func(*SessionClosedEventArgs))

SetClosedHandler sets the OnClosed callback in a thread-safe manner. Use this method instead of direct field assignment when the session is already connected, to avoid data races with the dispatch goroutine.

func (*Session) SetReportProgressHandler

func (s *Session) SetReportProgressHandler(handler func(Progress))

SetReportProgressHandler sets the OnReportProgress callback in a thread-safe manner. Use this method instead of direct field assignment when the session is already connected, to avoid data races with the dispatch goroutine.

func (*Session) SetRequestHandler

func (s *Session) SetRequestHandler(handler func(*RequestEventArgs))

SetRequestHandler sets the OnRequest callback in a thread-safe manner. Use this method instead of direct field assignment when the session is already connected, to avoid data races with the dispatch goroutine.

func (*Session) SetTraceHandler

func (s *Session) SetTraceHandler(handler TraceFunc)

SetTraceHandler sets the Trace callback in a thread-safe manner. Use this method instead of direct field assignment when the session is already connected, to avoid data races with the dispatch goroutine.

func (*Session) VerifyReconnectToken

func (s *Session) VerifyReconnectToken(previousSessionID, newSessionID, token []byte) (bool, error)

VerifyReconnectToken validates a reconnect token against the previous and new session IDs using the dedicated reconnect HMAC verifier.

type SessionClosedEventArgs

type SessionClosedEventArgs struct {
	Reason  messages.SSHDisconnectReason
	Message string
	Err     error
}

SessionClosedEventArgs contains arguments for session closed events.

type SessionConfig

type SessionConfig struct {
	// ProtocolExtensions lists protocol extensions enabled for the session.
	ProtocolExtensions []string

	// AuthenticationMethods lists enabled authentication methods.
	AuthenticationMethods []string

	// KeyExchangeAlgorithms lists enabled KEX algorithms in preference order.
	// A "none" entry means no key exchange (no security).
	KeyExchangeAlgorithms []string

	// PublicKeyAlgorithms lists enabled public key algorithms in preference order.
	PublicKeyAlgorithms []string

	// EncryptionAlgorithms lists enabled encryption algorithms in preference order.
	EncryptionAlgorithms []string

	// HmacAlgorithms lists enabled HMAC algorithms in preference order.
	HmacAlgorithms []string

	// CompressionAlgorithms lists enabled compression algorithms in preference order.
	CompressionAlgorithms []string

	// TraceChannelData enables tracing of all channel data messages.
	TraceChannelData bool

	// MaxClientAuthenticationAttempts is the max number of client auth attempts
	// allowed by the server. Default is 5.
	MaxClientAuthenticationAttempts int

	// EnableKeyExchangeGuess controls whether the client sends a KEX guess
	// before receiving server preferences. Disabled by default.
	EnableKeyExchangeGuess bool

	// KeepAliveIntervalSeconds is the keep-alive interval in seconds (0 = disabled).
	KeepAliveIntervalSeconds int

	// KeyRotationThreshold is the number of bytes after which key rotation is triggered.
	// Default is 512MB. This should only be changed for testing.
	KeyRotationThreshold int

	// MaxReconnectMessageCacheSize is the maximum number of sent messages to cache
	// for reconnection retransmission. When the cache exceeds this limit, the oldest
	// messages are evicted. Default is 1024.
	MaxReconnectMessageCacheSize int

	// MaxChannelWindowSize overrides the default channel window size.
	// Zero means use DefaultMaxWindowSize (1 MB).
	MaxChannelWindowSize uint32

	// MessageHandlers maps SSH message type numbers to custom handler functions.
	// When a message is received with a type that has a registered handler,
	// the handler is called instead of sending an UnimplementedMessage response.
	// This allows extending the SSH protocol without modifying the library.
	MessageHandlers map[byte]MessageHandler

	// ServiceRegistrations maps service names to their activation rules and factories.
	// Use AddService() to register services.
	ServiceRegistrations map[string]*ServiceRegistration
}

SessionConfig specifies the sets of algorithms and other configuration for an SSH session. Each collection of algorithms is in order of preference. Server and client negotiate the most-preferred algorithm supported by both.

func NewDefaultConfig

func NewDefaultConfig() *SessionConfig

NewDefaultConfig creates a new SessionConfig with default secure algorithms. The algorithm preference order matches the C# and TypeScript implementations.

func NewDefaultConfigWithReconnect

func NewDefaultConfigWithReconnect() *SessionConfig

NewDefaultConfigWithReconnect creates a new SessionConfig with default secure algorithms and reconnection extensions enabled.

func NewNoSecurityConfig

func NewNoSecurityConfig() *SessionConfig

NewNoSecurityConfig creates a new SessionConfig with no-security algorithms. All algorithm lists contain only "none", meaning no encryption, no HMAC, no key exchange.

func (*SessionConfig) AddService

func (c *SessionConfig) AddService(name string, activation ServiceActivation, factory ServiceFactory, config interface{})

AddService registers a service with activation rules in the session configuration.

func (*SessionConfig) Validate

func (c *SessionConfig) Validate() error

Validate checks the session configuration for internal consistency. It returns an error if algorithm combinations are invalid, such as encryption algorithms specified without any key exchange algorithm.

type SessionContour

type SessionContour struct {
	// contains filtered or unexported fields
}

SessionContour collects session metrics over time, producing an outline of the timing, speed, and quantity of bytes sent/received during the session.

Metrics are recorded across a number of equal time intervals. As the session time increases, intervals are expanded to keep the number of intervals under the configured maximum. Each expansion doubles the length of all intervals, while combining the metrics within each pair of combined intervals.

func ImportContour

func ImportContour(contourBase64 string) (*SessionContour, error)

ImportContour deserializes a session contour that was previously exported.

func NewSessionContour

func NewSessionContour(maxIntervals int) *SessionContour

NewSessionContour creates a new SessionContour with the given maximum intervals. maxIntervals must be a power of two and at least 2.

func (*SessionContour) AddUpdate

func (c *SessionContour) AddUpdate(u ContourUpdate)

AddUpdate directly adds a contour update. Useful for testing.

func (*SessionContour) BytesReceivedAt

func (c *SessionContour) BytesReceivedAt(i int) int64

BytesReceivedAt returns the bytes received for the interval at index i.

func (*SessionContour) BytesReceivedSlice

func (c *SessionContour) BytesReceivedSlice() []int64

BytesReceivedSlice returns a copy of the bytes received values for all recorded intervals.

func (*SessionContour) BytesSentAt

func (c *SessionContour) BytesSentAt(i int) int64

BytesSentAt returns the bytes sent for the interval at index i.

func (*SessionContour) BytesSentSlice

func (c *SessionContour) BytesSentSlice() []int64

BytesSentSlice returns a copy of the bytes sent values for all recorded intervals.

func (*SessionContour) CollectMetrics

func (c *SessionContour) CollectMetrics(metrics *SessionMetrics)

CollectMetrics starts collecting session metrics and processes them until Stop is called or the session is closed.

func (*SessionContour) Export

func (c *SessionContour) Export() string

Export serializes the session contour into a compact base64-encoded form.

func (*SessionContour) IntervalCount

func (c *SessionContour) IntervalCount() int

IntervalCount returns the current number of intervals with recorded metrics.

func (*SessionContour) IntervalMs

func (c *SessionContour) IntervalMs() int64

IntervalMs returns the current interval duration in milliseconds.

func (*SessionContour) LatencyAverageMsAt

func (c *SessionContour) LatencyAverageMsAt(i int) float32

LatencyAverageMsAt returns the average latency in ms for the interval at index i.

func (*SessionContour) LatencyAverageMsSlice

func (c *SessionContour) LatencyAverageMsSlice() []float32

LatencyAverageMsSlice returns a copy of the average latency values for all recorded intervals.

func (*SessionContour) LatencyMaxMsAt

func (c *SessionContour) LatencyMaxMsAt(i int) float32

LatencyMaxMsAt returns the maximum latency in ms for the interval at index i.

func (*SessionContour) LatencyMaxMsSlice

func (c *SessionContour) LatencyMaxMsSlice() []float32

LatencyMaxMsSlice returns a copy of the max latency values for all recorded intervals.

func (*SessionContour) LatencyMinMsAt

func (c *SessionContour) LatencyMinMsAt(i int) float32

LatencyMinMsAt returns the minimum latency in ms for the interval at index i.

func (*SessionContour) LatencyMinMsSlice

func (c *SessionContour) LatencyMinMsSlice() []float32

LatencyMinMsSlice returns a copy of the min latency values for all recorded intervals.

func (*SessionContour) MaxIntervals

func (c *SessionContour) MaxIntervals() int

MaxIntervals returns the maximum number of intervals this contour can record.

func (*SessionContour) Stop

func (c *SessionContour) Stop()

Stop signals the contour to stop collecting and waits for it to finish.

type SessionMetrics

type SessionMetrics struct {

	// OnMessageSent is called when a message is sent.
	// Args: (sessionTimeMs int64, sizeBytes int).
	OnMessageSent func(int64, int)

	// OnMessageReceived is called when a message is received.
	// Args: (sessionTimeMs int64, sizeBytes int).
	OnMessageReceived func(int64, int)

	// OnLatencyUpdated is called when latency is measured.
	// Args: (sessionTimeMs int64, latencyMs float32).
	OnLatencyUpdated func(int64, float32)

	// OnSessionClosed is called when the session is closed.
	OnSessionClosed func()
	// contains filtered or unexported fields
}

SessionMetrics tracks byte, message, and latency counters for an SSH session. Latency is measured in microseconds internally but reported in milliseconds.

func (*SessionMetrics) BytesReceived

func (m *SessionMetrics) BytesReceived() int64

BytesReceived returns the total number of wire bytes received on this session.

func (*SessionMetrics) BytesSent

func (m *SessionMetrics) BytesSent() int64

BytesSent returns the total number of wire bytes sent on this session.

func (*SessionMetrics) LatencyAverageMs

func (m *SessionMetrics) LatencyAverageMs() float32

LatencyAverageMs returns the average measured round-trip latency in milliseconds.

func (*SessionMetrics) LatencyCurrentMs

func (m *SessionMetrics) LatencyCurrentMs() float32

LatencyCurrentMs returns the most recent round-trip latency in milliseconds. Returns 0 if latency has not been measured or the session is disconnected.

func (*SessionMetrics) LatencyMaxMs

func (m *SessionMetrics) LatencyMaxMs() float32

LatencyMaxMs returns the maximum measured round-trip latency in milliseconds.

func (*SessionMetrics) LatencyMinMs

func (m *SessionMetrics) LatencyMinMs() float32

LatencyMinMs returns the minimum measured round-trip latency in milliseconds.

func (*SessionMetrics) MessagesReceived

func (m *SessionMetrics) MessagesReceived() int64

MessagesReceived returns the total number of messages received on this session.

func (*SessionMetrics) MessagesSent

func (m *SessionMetrics) MessagesSent() int64

MessagesSent returns the total number of messages sent on this session.

func (*SessionMetrics) Reconnections

func (m *SessionMetrics) Reconnections() int64

Reconnections returns the number of times this session has been reconnected.

func (*SessionMetrics) TimeMicroseconds

func (m *SessionMetrics) TimeMicroseconds() int64

TimeMicroseconds returns the current time in microseconds since Unix epoch. Used for latency tracking in reconnection support.

type Stream

type Stream struct {
	// contains filtered or unexported fields
}

Stream wraps an SSH Channel as an io.ReadWriteCloser. This allows using SSH channels with any Go library that expects a stream.

Stream does not support more than one concurrent reader or more than one concurrent writer.

func NewStream

func NewStream(channel *Channel) *Stream

NewStream creates a new Stream wrapping the given channel. The stream implements io.ReadWriteCloser.

func (*Stream) Channel

func (s *Stream) Channel() *Channel

Channel returns the underlying SSH channel.

func (*Stream) Close

func (s *Stream) Close() error

Close closes the underlying channel. Close implements io.Closer.

func (*Stream) Read

func (s *Stream) Read(p []byte) (int, error)

Read reads data from the channel into p. It blocks until data is available, the channel is closed, or an error occurs. Read implements io.Reader.

func (*Stream) Write

func (s *Stream) Write(p []byte) (int, error)

Write sends data on the channel. Write implements io.Writer.

func (*Stream) WriteContext

func (s *Stream) WriteContext(ctx context.Context, p []byte) (int, error)

WriteContext sends data on the channel, respecting the provided context for cancellation and timeouts.

type TraceFunc

type TraceFunc func(level TraceLevel, eventID int, message string)

TraceFunc is the function signature for handling SSH trace events. level is the severity level of the event. eventID is an integer that identifies the type of event (one of the SSHTraceEventID constants). message is a human-readable description of the event.

type TraceLevel

type TraceLevel int

TraceLevel represents the severity level of a trace event.

const (
	// TraceLevelError indicates an error event.
	TraceLevelError TraceLevel = iota
	// TraceLevelWarning indicates a warning event.
	TraceLevelWarning
	// TraceLevelInfo indicates an informational event.
	TraceLevelInfo
	// TraceLevelVerbose indicates a verbose/debug event.
	TraceLevelVerbose
)

func (TraceLevel) String

func (l TraceLevel) String() string

String returns the string representation of a TraceLevel.

type VersionInfo

type VersionInfo struct {
	ProtocolVersion string
	Name            string
	Version         string
	// contains filtered or unexported fields
}

VersionInfo contains parsed SSH version information from the version string exchanged during the initial SSH handshake.

func GetLocalVersion

func GetLocalVersion() *VersionInfo

GetLocalVersion returns the version info for this SSH library.

func ParseVersionInfo

func ParseVersionInfo(versionString string) *VersionInfo

Parse attempts to parse an SSH version string (e.g., "SSH-2.0-OpenSSH_7.9") into a VersionInfo. Returns nil if the string cannot be parsed.

func (*VersionInfo) IsDevTunnelsSSH

func (v *VersionInfo) IsDevTunnelsSSH() bool

IsDevTunnelsSSH returns true if this version info represents some version of the Dev Tunnels SSH library (C#, TypeScript, or Go).

func (*VersionInfo) String

func (v *VersionInfo) String() string

String returns the original SSH version string.

Directories

Path Synopsis
Package algorithms provides cryptographic algorithm implementations for SSH key exchange, encryption, and message authentication.
Package algorithms provides cryptographic algorithm implementations for SSH key exchange, encryption, and message authentication.
Package sshio provides binary data readers and writers for the SSH wire protocol.
Package sshio provides binary data readers and writers for the SSH wire protocol.
Package messages defines SSH protocol message types and their serialization.
Package messages defines SSH protocol message types and their serialization.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL