Francisco J Ballesteroszxfs put fix1449f3310 years agoHistory
477 lines · 13.8 KB · Go
RawDownload
// 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.struct Permissions {	// 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.struct ServerConfig {	// 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)} // 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.struct cachedPubKey {	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.struct pubKeyCache {	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// serverstruct ServerConn {	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")	} 	var err error	s.serverVersion = []byte(packageVersion)	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.requestKeyChange(); 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])	} 	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.struct sshClientKeyboardInteractive {	*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}