mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-11-04 04:28:08 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			496 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			496 lines
		
	
	
		
			14 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"
 | 
						|
	"io"
 | 
						|
	"net"
 | 
						|
)
 | 
						|
 | 
						|
// The Permissions type holds fine-grained permissions that are
 | 
						|
// specific to a user or a specific authentication method for a
 | 
						|
// user. Permissions, except for "source-address", must be enforced in
 | 
						|
// the server application layer, after successful authentication. The
 | 
						|
// Permissions are passed on in ServerConn so a server implementation
 | 
						|
// can honor them.
 | 
						|
type Permissions struct {
 | 
						|
	// Critical options restrict default permissions. Common
 | 
						|
	// restrictions are "source-address" and "force-command". If
 | 
						|
	// the server cannot enforce the restriction, or does not
 | 
						|
	// recognize it, the user should not authenticate.
 | 
						|
	CriticalOptions map[string]string
 | 
						|
 | 
						|
	// Extensions are extra functionality that the server may
 | 
						|
	// offer on authenticated connections. Common extensions are
 | 
						|
	// "permit-agent-forwarding", "permit-X11-forwarding". Lack of
 | 
						|
	// support for an extension does not preclude authenticating a
 | 
						|
	// user.
 | 
						|
	Extensions map[string]string
 | 
						|
}
 | 
						|
 | 
						|
// ServerConfig holds server specific configuration data.
 | 
						|
type ServerConfig struct {
 | 
						|
	// Config contains configuration shared between client and server.
 | 
						|
	Config
 | 
						|
 | 
						|
	hostKeys []Signer
 | 
						|
 | 
						|
	// NoClientAuth is true if clients are allowed to connect without
 | 
						|
	// authenticating.
 | 
						|
	NoClientAuth bool
 | 
						|
 | 
						|
	// PasswordCallback, if non-nil, is called when a user
 | 
						|
	// attempts to authenticate using a password.
 | 
						|
	PasswordCallback func(conn ConnMetadata, password []byte) (*Permissions, error)
 | 
						|
 | 
						|
	// PublicKeyCallback, if non-nil, is called when a client attempts public
 | 
						|
	// key authentication. It must return true if the given public key is
 | 
						|
	// valid for the given user. For example, see CertChecker.Authenticate.
 | 
						|
	PublicKeyCallback func(conn ConnMetadata, key PublicKey) (*Permissions, error)
 | 
						|
 | 
						|
	// KeyboardInteractiveCallback, if non-nil, is called when
 | 
						|
	// keyboard-interactive authentication is selected (RFC
 | 
						|
	// 4256). The client object's Challenge function should be
 | 
						|
	// used to query the user. The callback may offer multiple
 | 
						|
	// Challenge rounds. To avoid information leaks, the client
 | 
						|
	// should be presented a challenge even if the user is
 | 
						|
	// unknown.
 | 
						|
	KeyboardInteractiveCallback func(conn ConnMetadata, client KeyboardInteractiveChallenge) (*Permissions, error)
 | 
						|
 | 
						|
	// AuthLogCallback, if non-nil, is called to log all authentication
 | 
						|
	// attempts.
 | 
						|
	AuthLogCallback func(conn ConnMetadata, method string, err error)
 | 
						|
 | 
						|
	// ServerVersion is the version identification string to announce in
 | 
						|
	// the public handshake.
 | 
						|
	// If empty, a reasonable default is used.
 | 
						|
	// Note that RFC 4253 section 4.2 requires that this string start with
 | 
						|
	// "SSH-2.0-".
 | 
						|
	ServerVersion string
 | 
						|
}
 | 
						|
 | 
						|
// AddHostKey adds a private key as a host key. If an existing host
 | 
						|
// key exists with the same algorithm, it is overwritten. Each server
 | 
						|
// config must have at least one host key.
 | 
						|
func (s *ServerConfig) AddHostKey(key Signer) {
 | 
						|
	for i, k := range s.hostKeys {
 | 
						|
		if k.PublicKey().Type() == key.PublicKey().Type() {
 | 
						|
			s.hostKeys[i] = key
 | 
						|
			return
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	s.hostKeys = append(s.hostKeys, key)
 | 
						|
}
 | 
						|
 | 
						|
// cachedPubKey contains the results of querying whether a public key is
 | 
						|
// acceptable for a user.
 | 
						|
type cachedPubKey struct {
 | 
						|
	user       string
 | 
						|
	pubKeyData []byte
 | 
						|
	result     error
 | 
						|
	perms      *Permissions
 | 
						|
}
 | 
						|
 | 
						|
const maxCachedPubKeys = 16
 | 
						|
 | 
						|
// pubKeyCache caches tests for public keys.  Since SSH clients
 | 
						|
// will query whether a public key is acceptable before attempting to
 | 
						|
// authenticate with it, we end up with duplicate queries for public
 | 
						|
// key validity.  The cache only applies to a single ServerConn.
 | 
						|
type pubKeyCache struct {
 | 
						|
	keys []cachedPubKey
 | 
						|
}
 | 
						|
 | 
						|
// get returns the result for a given user/algo/key tuple.
 | 
						|
func (c *pubKeyCache) get(user string, pubKeyData []byte) (cachedPubKey, bool) {
 | 
						|
	for _, k := range c.keys {
 | 
						|
		if k.user == user && bytes.Equal(k.pubKeyData, pubKeyData) {
 | 
						|
			return k, true
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return cachedPubKey{}, false
 | 
						|
}
 | 
						|
 | 
						|
// add adds the given tuple to the cache.
 | 
						|
func (c *pubKeyCache) add(candidate cachedPubKey) {
 | 
						|
	if len(c.keys) < maxCachedPubKeys {
 | 
						|
		c.keys = append(c.keys, candidate)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// ServerConn is an authenticated SSH connection, as seen from the
 | 
						|
// server
 | 
						|
type ServerConn struct {
 | 
						|
	Conn
 | 
						|
 | 
						|
	// If the succeeding authentication callback returned a
 | 
						|
	// non-nil Permissions pointer, it is stored here.
 | 
						|
	Permissions *Permissions
 | 
						|
}
 | 
						|
 | 
						|
// NewServerConn starts a new SSH server with c as the underlying
 | 
						|
// transport.  It starts with a handshake and, if the handshake is
 | 
						|
// unsuccessful, it closes the connection and returns an error.  The
 | 
						|
// Request and NewChannel channels must be serviced, or the connection
 | 
						|
// will hang.
 | 
						|
func NewServerConn(c net.Conn, config *ServerConfig) (*ServerConn, <-chan NewChannel, <-chan *Request, error) {
 | 
						|
	fullConf := *config
 | 
						|
	fullConf.SetDefaults()
 | 
						|
	s := &connection{
 | 
						|
		sshConn: sshConn{conn: c},
 | 
						|
	}
 | 
						|
	perms, err := s.serverHandshake(&fullConf)
 | 
						|
	if err != nil {
 | 
						|
		c.Close()
 | 
						|
		return nil, nil, nil, err
 | 
						|
	}
 | 
						|
	return &ServerConn{s, perms}, s.mux.incomingChannels, s.mux.incomingRequests, nil
 | 
						|
}
 | 
						|
 | 
						|
// signAndMarshal signs the data with the appropriate algorithm,
 | 
						|
// and serializes the result in SSH wire format.
 | 
						|
func signAndMarshal(k Signer, rand io.Reader, data []byte) ([]byte, error) {
 | 
						|
	sig, err := k.Sign(rand, data)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return Marshal(sig), nil
 | 
						|
}
 | 
						|
 | 
						|
// handshake performs key exchange and user authentication.
 | 
						|
func (s *connection) serverHandshake(config *ServerConfig) (*Permissions, error) {
 | 
						|
	if len(config.hostKeys) == 0 {
 | 
						|
		return nil, errors.New("ssh: server has no host keys")
 | 
						|
	}
 | 
						|
 | 
						|
	if !config.NoClientAuth && config.PasswordCallback == nil && config.PublicKeyCallback == nil && config.KeyboardInteractiveCallback == nil {
 | 
						|
		return nil, errors.New("ssh: no authentication methods configured but NoClientAuth is also false")
 | 
						|
	}
 | 
						|
 | 
						|
	if config.ServerVersion != "" {
 | 
						|
		s.serverVersion = []byte(config.ServerVersion)
 | 
						|
	} else {
 | 
						|
		s.serverVersion = []byte(packageVersion)
 | 
						|
	}
 | 
						|
	var err error
 | 
						|
	s.clientVersion, err = exchangeVersions(s.sshConn.conn, s.serverVersion)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	tr := newTransport(s.sshConn.conn, config.Rand, false /* not client */)
 | 
						|
	s.transport = newServerTransport(tr, s.clientVersion, s.serverVersion, config)
 | 
						|
 | 
						|
	if err := s.transport.requestInitialKeyChange(); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	if packet, err := s.transport.readPacket(); err != nil {
 | 
						|
		return nil, err
 | 
						|
	} else if packet[0] != msgNewKeys {
 | 
						|
		return nil, unexpectedMessageError(msgNewKeys, packet[0])
 | 
						|
	}
 | 
						|
 | 
						|
	// We just did the key change, so the session ID is established.
 | 
						|
	s.sessionID = s.transport.getSessionID()
 | 
						|
 | 
						|
	var packet []byte
 | 
						|
	if packet, err = s.transport.readPacket(); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	var serviceRequest serviceRequestMsg
 | 
						|
	if err = Unmarshal(packet, &serviceRequest); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	if serviceRequest.Service != serviceUserAuth {
 | 
						|
		return nil, errors.New("ssh: requested service '" + serviceRequest.Service + "' before authenticating")
 | 
						|
	}
 | 
						|
	serviceAccept := serviceAcceptMsg{
 | 
						|
		Service: serviceUserAuth,
 | 
						|
	}
 | 
						|
	if err := s.transport.writePacket(Marshal(&serviceAccept)); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	perms, err := s.serverAuthenticate(config)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	s.mux = newMux(s.transport)
 | 
						|
	return perms, err
 | 
						|
}
 | 
						|
 | 
						|
func isAcceptableAlgo(algo string) bool {
 | 
						|
	switch algo {
 | 
						|
	case KeyAlgoRSA, KeyAlgoDSA, KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521,
 | 
						|
		CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01:
 | 
						|
		return true
 | 
						|
	}
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
func checkSourceAddress(addr net.Addr, sourceAddr string) error {
 | 
						|
	if addr == nil {
 | 
						|
		return errors.New("ssh: no address known for client, but source-address match required")
 | 
						|
	}
 | 
						|
 | 
						|
	tcpAddr, ok := addr.(*net.TCPAddr)
 | 
						|
	if !ok {
 | 
						|
		return fmt.Errorf("ssh: remote address %v is not an TCP address when checking source-address match", addr)
 | 
						|
	}
 | 
						|
 | 
						|
	if allowedIP := net.ParseIP(sourceAddr); allowedIP != nil {
 | 
						|
		if bytes.Equal(allowedIP, tcpAddr.IP) {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		_, ipNet, err := net.ParseCIDR(sourceAddr)
 | 
						|
		if err != nil {
 | 
						|
			return fmt.Errorf("ssh: error parsing source-address restriction %q: %v", sourceAddr, err)
 | 
						|
		}
 | 
						|
 | 
						|
		if ipNet.Contains(tcpAddr.IP) {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return fmt.Errorf("ssh: remote address %v is not allowed because of source-address restriction", addr)
 | 
						|
}
 | 
						|
 | 
						|
func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, error) {
 | 
						|
	var err error
 | 
						|
	var cache pubKeyCache
 | 
						|
	var perms *Permissions
 | 
						|
 | 
						|
userAuthLoop:
 | 
						|
	for {
 | 
						|
		var userAuthReq userAuthRequestMsg
 | 
						|
		if packet, err := s.transport.readPacket(); err != nil {
 | 
						|
			return nil, err
 | 
						|
		} else if err = Unmarshal(packet, &userAuthReq); err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
 | 
						|
		if userAuthReq.Service != serviceSSH {
 | 
						|
			return nil, errors.New("ssh: client attempted to negotiate for unknown service: " + userAuthReq.Service)
 | 
						|
		}
 | 
						|
 | 
						|
		s.user = userAuthReq.User
 | 
						|
		perms = nil
 | 
						|
		authErr := errors.New("no auth passed yet")
 | 
						|
 | 
						|
		switch userAuthReq.Method {
 | 
						|
		case "none":
 | 
						|
			if config.NoClientAuth {
 | 
						|
				s.user = ""
 | 
						|
				authErr = nil
 | 
						|
			}
 | 
						|
		case "password":
 | 
						|
			if config.PasswordCallback == nil {
 | 
						|
				authErr = errors.New("ssh: password auth not configured")
 | 
						|
				break
 | 
						|
			}
 | 
						|
			payload := userAuthReq.Payload
 | 
						|
			if len(payload) < 1 || payload[0] != 0 {
 | 
						|
				return nil, parseError(msgUserAuthRequest)
 | 
						|
			}
 | 
						|
			payload = payload[1:]
 | 
						|
			password, payload, ok := parseString(payload)
 | 
						|
			if !ok || len(payload) > 0 {
 | 
						|
				return nil, parseError(msgUserAuthRequest)
 | 
						|
			}
 | 
						|
 | 
						|
			perms, authErr = config.PasswordCallback(s, password)
 | 
						|
		case "keyboard-interactive":
 | 
						|
			if config.KeyboardInteractiveCallback == nil {
 | 
						|
				authErr = errors.New("ssh: keyboard-interactive auth not configubred")
 | 
						|
				break
 | 
						|
			}
 | 
						|
 | 
						|
			prompter := &sshClientKeyboardInteractive{s}
 | 
						|
			perms, authErr = config.KeyboardInteractiveCallback(s, prompter.Challenge)
 | 
						|
		case "publickey":
 | 
						|
			if config.PublicKeyCallback == nil {
 | 
						|
				authErr = errors.New("ssh: publickey auth not configured")
 | 
						|
				break
 | 
						|
			}
 | 
						|
			payload := userAuthReq.Payload
 | 
						|
			if len(payload) < 1 {
 | 
						|
				return nil, parseError(msgUserAuthRequest)
 | 
						|
			}
 | 
						|
			isQuery := payload[0] == 0
 | 
						|
			payload = payload[1:]
 | 
						|
			algoBytes, payload, ok := parseString(payload)
 | 
						|
			if !ok {
 | 
						|
				return nil, parseError(msgUserAuthRequest)
 | 
						|
			}
 | 
						|
			algo := string(algoBytes)
 | 
						|
			if !isAcceptableAlgo(algo) {
 | 
						|
				authErr = fmt.Errorf("ssh: algorithm %q not accepted", algo)
 | 
						|
				break
 | 
						|
			}
 | 
						|
 | 
						|
			pubKeyData, payload, ok := parseString(payload)
 | 
						|
			if !ok {
 | 
						|
				return nil, parseError(msgUserAuthRequest)
 | 
						|
			}
 | 
						|
 | 
						|
			pubKey, err := ParsePublicKey(pubKeyData)
 | 
						|
			if err != nil {
 | 
						|
				return nil, err
 | 
						|
			}
 | 
						|
 | 
						|
			candidate, ok := cache.get(s.user, pubKeyData)
 | 
						|
			if !ok {
 | 
						|
				candidate.user = s.user
 | 
						|
				candidate.pubKeyData = pubKeyData
 | 
						|
				candidate.perms, candidate.result = config.PublicKeyCallback(s, pubKey)
 | 
						|
				if candidate.result == nil && candidate.perms != nil && candidate.perms.CriticalOptions != nil && candidate.perms.CriticalOptions[sourceAddressCriticalOption] != "" {
 | 
						|
					candidate.result = checkSourceAddress(
 | 
						|
						s.RemoteAddr(),
 | 
						|
						candidate.perms.CriticalOptions[sourceAddressCriticalOption])
 | 
						|
				}
 | 
						|
				cache.add(candidate)
 | 
						|
			}
 | 
						|
 | 
						|
			if isQuery {
 | 
						|
				// The client can query if the given public key
 | 
						|
				// would be okay.
 | 
						|
				if len(payload) > 0 {
 | 
						|
					return nil, parseError(msgUserAuthRequest)
 | 
						|
				}
 | 
						|
 | 
						|
				if candidate.result == nil {
 | 
						|
					okMsg := userAuthPubKeyOkMsg{
 | 
						|
						Algo:   algo,
 | 
						|
						PubKey: pubKeyData,
 | 
						|
					}
 | 
						|
					if err = s.transport.writePacket(Marshal(&okMsg)); err != nil {
 | 
						|
						return nil, err
 | 
						|
					}
 | 
						|
					continue userAuthLoop
 | 
						|
				}
 | 
						|
				authErr = candidate.result
 | 
						|
			} else {
 | 
						|
				sig, payload, ok := parseSignature(payload)
 | 
						|
				if !ok || len(payload) > 0 {
 | 
						|
					return nil, parseError(msgUserAuthRequest)
 | 
						|
				}
 | 
						|
				// Ensure the public key algo and signature algo
 | 
						|
				// are supported.  Compare the private key
 | 
						|
				// algorithm name that corresponds to algo with
 | 
						|
				// sig.Format.  This is usually the same, but
 | 
						|
				// for certs, the names differ.
 | 
						|
				if !isAcceptableAlgo(sig.Format) {
 | 
						|
					break
 | 
						|
				}
 | 
						|
				signedData := buildDataSignedForAuth(s.transport.getSessionID(), userAuthReq, algoBytes, pubKeyData)
 | 
						|
 | 
						|
				if err := pubKey.Verify(signedData, sig); err != nil {
 | 
						|
					return nil, err
 | 
						|
				}
 | 
						|
 | 
						|
				authErr = candidate.result
 | 
						|
				perms = candidate.perms
 | 
						|
			}
 | 
						|
		default:
 | 
						|
			authErr = fmt.Errorf("ssh: unknown method %q", userAuthReq.Method)
 | 
						|
		}
 | 
						|
 | 
						|
		if config.AuthLogCallback != nil {
 | 
						|
			config.AuthLogCallback(s, userAuthReq.Method, authErr)
 | 
						|
		}
 | 
						|
 | 
						|
		if authErr == nil {
 | 
						|
			break userAuthLoop
 | 
						|
		}
 | 
						|
 | 
						|
		var failureMsg userAuthFailureMsg
 | 
						|
		if config.PasswordCallback != nil {
 | 
						|
			failureMsg.Methods = append(failureMsg.Methods, "password")
 | 
						|
		}
 | 
						|
		if config.PublicKeyCallback != nil {
 | 
						|
			failureMsg.Methods = append(failureMsg.Methods, "publickey")
 | 
						|
		}
 | 
						|
		if config.KeyboardInteractiveCallback != nil {
 | 
						|
			failureMsg.Methods = append(failureMsg.Methods, "keyboard-interactive")
 | 
						|
		}
 | 
						|
 | 
						|
		if len(failureMsg.Methods) == 0 {
 | 
						|
			return nil, errors.New("ssh: no authentication methods configured but NoClientAuth is also false")
 | 
						|
		}
 | 
						|
 | 
						|
		if err = s.transport.writePacket(Marshal(&failureMsg)); err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if err = s.transport.writePacket([]byte{msgUserAuthSuccess}); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return perms, nil
 | 
						|
}
 | 
						|
 | 
						|
// sshClientKeyboardInteractive implements a ClientKeyboardInteractive by
 | 
						|
// asking the client on the other side of a ServerConn.
 | 
						|
type sshClientKeyboardInteractive struct {
 | 
						|
	*connection
 | 
						|
}
 | 
						|
 | 
						|
func (c *sshClientKeyboardInteractive) Challenge(user, instruction string, questions []string, echos []bool) (answers []string, err error) {
 | 
						|
	if len(questions) != len(echos) {
 | 
						|
		return nil, errors.New("ssh: echos and questions must have equal length")
 | 
						|
	}
 | 
						|
 | 
						|
	var prompts []byte
 | 
						|
	for i := range questions {
 | 
						|
		prompts = appendString(prompts, questions[i])
 | 
						|
		prompts = appendBool(prompts, echos[i])
 | 
						|
	}
 | 
						|
 | 
						|
	if err := c.transport.writePacket(Marshal(&userAuthInfoRequestMsg{
 | 
						|
		Instruction: instruction,
 | 
						|
		NumPrompts:  uint32(len(questions)),
 | 
						|
		Prompts:     prompts,
 | 
						|
	})); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	packet, err := c.transport.readPacket()
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	if packet[0] != msgUserAuthInfoResponse {
 | 
						|
		return nil, unexpectedMessageError(msgUserAuthInfoResponse, packet[0])
 | 
						|
	}
 | 
						|
	packet = packet[1:]
 | 
						|
 | 
						|
	n, packet, ok := parseUint32(packet)
 | 
						|
	if !ok || int(n) != len(questions) {
 | 
						|
		return nil, parseError(msgUserAuthInfoResponse)
 | 
						|
	}
 | 
						|
 | 
						|
	for i := uint32(0); i < n; i++ {
 | 
						|
		ans, rest, ok := parseString(packet)
 | 
						|
		if !ok {
 | 
						|
			return nil, parseError(msgUserAuthInfoResponse)
 | 
						|
		}
 | 
						|
 | 
						|
		answers = append(answers, string(ans))
 | 
						|
		packet = rest
 | 
						|
	}
 | 
						|
	if len(packet) != 0 {
 | 
						|
		return nil, errors.New("ssh: junk at end of message")
 | 
						|
	}
 | 
						|
 | 
						|
	return answers, nil
 | 
						|
}
 |