RFC 8332, Section 2 sets up two overlapping namespaces: public key formats and public key algorithms. * The formats are what we currently have KeyAlgo constants for, and they appear in PublicKey.Type. * The algorithms are the set of both KeyAlgo and SigAlgo constants, and they appear in Signature.Format (amongst other places). This is incoherent, because that means Signature.Format can be both a KeyAlgo (like KeyAlgoECDSA256) or a SigAlgo (like SigAlgoRSASHA2256). One solution would be to duplicate all the KeyAlgo constants into the SigAlgo namespace, but that would be confusing because applications are currently using KeyAlgos where they'd be supposed to use the new SigAlgos (while we can't deprecate the KeyAlgos because they are still necessary for the PublicKey.Type namespace). Instead, drop the separate namespaces, and use KeyAlgos throughout. There are simply some KeyAlgos that can't be a PublicKey.Type. Take the opportunity to fix the stuttering SHA22565/SHA2512 names. It's totally ok to call those hashes SHA-256 and SHA-512 without the family infix. For golang/go#49952 Change-Id: Ia1fce3912a7e60aa70a88f75ed311be331fd19d5 Reviewed-on: https://go-review.googlesource.com/c/crypto/+/392354 Trust: Filippo Valsorda <filippo@golang.org> Run-TryBot: Filippo Valsorda <filippo@golang.org> Reviewed-by: Roland Shoemaker <roland@golang.org> TryBot-Result: Gopher Robot <gobot@golang.org>
292 lines
8.8 KiB
Go
292 lines
8.8 KiB
Go
// Copyright 2011 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package ssh
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// Client implements a traditional SSH client that supports shells,
|
|
// subprocesses, TCP port/streamlocal forwarding and tunneled dialing.
|
|
type Client struct {
|
|
Conn
|
|
|
|
handleForwardsOnce sync.Once // guards calling (*Client).handleForwards
|
|
|
|
forwards forwardList // forwarded tcpip connections from the remote side
|
|
mu sync.Mutex
|
|
channelHandlers map[string]chan NewChannel
|
|
}
|
|
|
|
// HandleChannelOpen returns a channel on which NewChannel requests
|
|
// for the given type are sent. If the type already is being handled,
|
|
// nil is returned. The channel is closed when the connection is closed.
|
|
func (c *Client) HandleChannelOpen(channelType string) <-chan NewChannel {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
if c.channelHandlers == nil {
|
|
// The SSH channel has been closed.
|
|
c := make(chan NewChannel)
|
|
close(c)
|
|
return c
|
|
}
|
|
|
|
ch := c.channelHandlers[channelType]
|
|
if ch != nil {
|
|
return nil
|
|
}
|
|
|
|
ch = make(chan NewChannel, chanSize)
|
|
c.channelHandlers[channelType] = ch
|
|
return ch
|
|
}
|
|
|
|
// NewClient creates a Client on top of the given connection.
|
|
func NewClient(c Conn, chans <-chan NewChannel, reqs <-chan *Request) *Client {
|
|
conn := &Client{
|
|
Conn: c,
|
|
channelHandlers: make(map[string]chan NewChannel, 1),
|
|
}
|
|
|
|
go conn.handleGlobalRequests(reqs)
|
|
go conn.handleChannelOpens(chans)
|
|
go func() {
|
|
conn.Wait()
|
|
conn.forwards.closeAll()
|
|
}()
|
|
return conn
|
|
}
|
|
|
|
// NewClientConn establishes an authenticated SSH connection using c
|
|
// as the underlying transport. The Request and NewChannel channels
|
|
// must be serviced or the connection will hang.
|
|
func NewClientConn(c net.Conn, addr string, config *ClientConfig) (Conn, <-chan NewChannel, <-chan *Request, error) {
|
|
fullConf := *config
|
|
fullConf.SetDefaults()
|
|
if fullConf.HostKeyCallback == nil {
|
|
c.Close()
|
|
return nil, nil, nil, errors.New("ssh: must specify HostKeyCallback")
|
|
}
|
|
|
|
conn := &connection{
|
|
sshConn: sshConn{conn: c, user: fullConf.User},
|
|
}
|
|
|
|
if err := conn.clientHandshake(addr, &fullConf); err != nil {
|
|
c.Close()
|
|
return nil, nil, nil, fmt.Errorf("ssh: handshake failed: %v", err)
|
|
}
|
|
conn.mux = newMux(conn.transport)
|
|
return conn, conn.mux.incomingChannels, conn.mux.incomingRequests, nil
|
|
}
|
|
|
|
// clientHandshake performs the client side key exchange. See RFC 4253 Section
|
|
// 7.
|
|
func (c *connection) clientHandshake(dialAddress string, config *ClientConfig) error {
|
|
if config.ClientVersion != "" {
|
|
c.clientVersion = []byte(config.ClientVersion)
|
|
} else {
|
|
c.clientVersion = []byte(packageVersion)
|
|
}
|
|
var err error
|
|
c.serverVersion, err = exchangeVersions(c.sshConn.conn, c.clientVersion)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
c.transport = newClientTransport(
|
|
newTransport(c.sshConn.conn, config.Rand, true /* is client */),
|
|
c.clientVersion, c.serverVersion, config, dialAddress, c.sshConn.RemoteAddr())
|
|
if err := c.transport.waitSession(); err != nil {
|
|
return err
|
|
}
|
|
|
|
c.sessionID = c.transport.getSessionID()
|
|
return c.clientAuthenticate(config)
|
|
}
|
|
|
|
// verifyHostKeySignature verifies the host key obtained in the key
|
|
// exchange.
|
|
func verifyHostKeySignature(hostKey PublicKey, algo string, result *kexResult) error {
|
|
sig, rest, ok := parseSignatureBody(result.Signature)
|
|
if len(rest) > 0 || !ok {
|
|
return errors.New("ssh: signature parse error")
|
|
}
|
|
|
|
// For keys, underlyingAlgo is exactly algo. For certificates,
|
|
// we have to look up the underlying key algorithm that SSH
|
|
// uses to evaluate signatures.
|
|
underlyingAlgo := algo
|
|
for sigAlgo, certAlgo := range certAlgoNames {
|
|
if certAlgo == algo {
|
|
underlyingAlgo = sigAlgo
|
|
}
|
|
}
|
|
if sig.Format != underlyingAlgo {
|
|
return fmt.Errorf("ssh: invalid signature algorithm %q, expected %q", sig.Format, underlyingAlgo)
|
|
}
|
|
|
|
return hostKey.Verify(result.H, sig)
|
|
}
|
|
|
|
// NewSession opens a new Session for this client. (A session is a remote
|
|
// execution of a program.)
|
|
func (c *Client) NewSession() (*Session, error) {
|
|
ch, in, err := c.OpenChannel("session", nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return newSession(ch, in)
|
|
}
|
|
|
|
func (c *Client) handleGlobalRequests(incoming <-chan *Request) {
|
|
for r := range incoming {
|
|
// This handles keepalive messages and matches
|
|
// the behaviour of OpenSSH.
|
|
r.Reply(false, nil)
|
|
}
|
|
}
|
|
|
|
// handleChannelOpens channel open messages from the remote side.
|
|
func (c *Client) handleChannelOpens(in <-chan NewChannel) {
|
|
for ch := range in {
|
|
c.mu.Lock()
|
|
handler := c.channelHandlers[ch.ChannelType()]
|
|
c.mu.Unlock()
|
|
|
|
if handler != nil {
|
|
handler <- ch
|
|
} else {
|
|
ch.Reject(UnknownChannelType, fmt.Sprintf("unknown channel type: %v", ch.ChannelType()))
|
|
}
|
|
}
|
|
|
|
c.mu.Lock()
|
|
for _, ch := range c.channelHandlers {
|
|
close(ch)
|
|
}
|
|
c.channelHandlers = nil
|
|
c.mu.Unlock()
|
|
}
|
|
|
|
// Dial starts a client connection to the given SSH server. It is a
|
|
// convenience function that connects to the given network address,
|
|
// initiates the SSH handshake, and then sets up a Client. For access
|
|
// to incoming channels and requests, use net.Dial with NewClientConn
|
|
// instead.
|
|
func Dial(network, addr string, config *ClientConfig) (*Client, error) {
|
|
conn, err := net.DialTimeout(network, addr, config.Timeout)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
c, chans, reqs, err := NewClientConn(conn, addr, config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return NewClient(c, chans, reqs), nil
|
|
}
|
|
|
|
// HostKeyCallback is the function type used for verifying server
|
|
// keys. A HostKeyCallback must return nil if the host key is OK, or
|
|
// an error to reject it. It receives the hostname as passed to Dial
|
|
// or NewClientConn. The remote address is the RemoteAddr of the
|
|
// net.Conn underlying the SSH connection.
|
|
type HostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error
|
|
|
|
// BannerCallback is the function type used for treat the banner sent by
|
|
// the server. A BannerCallback receives the message sent by the remote server.
|
|
type BannerCallback func(message string) error
|
|
|
|
// A ClientConfig structure is used to configure a Client. It must not be
|
|
// modified after having been passed to an SSH function.
|
|
type ClientConfig struct {
|
|
// Config contains configuration that is shared between clients and
|
|
// servers.
|
|
Config
|
|
|
|
// User contains the username to authenticate as.
|
|
User string
|
|
|
|
// Auth contains possible authentication methods to use with the
|
|
// server. Only the first instance of a particular RFC 4252 method will
|
|
// be used during authentication.
|
|
Auth []AuthMethod
|
|
|
|
// HostKeyCallback is called during the cryptographic
|
|
// handshake to validate the server's host key. The client
|
|
// configuration must supply this callback for the connection
|
|
// to succeed. The functions InsecureIgnoreHostKey or
|
|
// FixedHostKey can be used for simplistic host key checks.
|
|
HostKeyCallback HostKeyCallback
|
|
|
|
// BannerCallback is called during the SSH dance to display a custom
|
|
// server's message. The client configuration can supply this callback to
|
|
// handle it as wished. The function BannerDisplayStderr can be used for
|
|
// simplistic display on Stderr.
|
|
BannerCallback BannerCallback
|
|
|
|
// ClientVersion contains the version identification string that will
|
|
// be used for the connection. If empty, a reasonable default is used.
|
|
ClientVersion string
|
|
|
|
// HostKeyAlgorithms lists the public key algorithms that the client will
|
|
// accept from the server for host key authentication, in order of
|
|
// preference. If empty, a reasonable default is used. Any
|
|
// string returned from a PublicKey.Type method may be used, or
|
|
// any of the CertAlgo and KeyAlgo constants.
|
|
HostKeyAlgorithms []string
|
|
|
|
// Timeout is the maximum amount of time for the TCP connection to establish.
|
|
//
|
|
// A Timeout of zero means no timeout.
|
|
Timeout time.Duration
|
|
}
|
|
|
|
// InsecureIgnoreHostKey returns a function that can be used for
|
|
// ClientConfig.HostKeyCallback to accept any host key. It should
|
|
// not be used for production code.
|
|
func InsecureIgnoreHostKey() HostKeyCallback {
|
|
return func(hostname string, remote net.Addr, key PublicKey) error {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
type fixedHostKey struct {
|
|
key PublicKey
|
|
}
|
|
|
|
func (f *fixedHostKey) check(hostname string, remote net.Addr, key PublicKey) error {
|
|
if f.key == nil {
|
|
return fmt.Errorf("ssh: required host key was nil")
|
|
}
|
|
if !bytes.Equal(key.Marshal(), f.key.Marshal()) {
|
|
return fmt.Errorf("ssh: host key mismatch")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// FixedHostKey returns a function for use in
|
|
// ClientConfig.HostKeyCallback to accept only a specific host key.
|
|
func FixedHostKey(key PublicKey) HostKeyCallback {
|
|
hk := &fixedHostKey{key}
|
|
return hk.check
|
|
}
|
|
|
|
// BannerDisplayStderr returns a function that can be used for
|
|
// ClientConfig.BannerCallback to display banners on os.Stderr.
|
|
func BannerDisplayStderr() BannerCallback {
|
|
return func(banner string) error {
|
|
_, err := os.Stderr.WriteString(banner)
|
|
|
|
return err
|
|
}
|
|
}
|